diff options
Diffstat (limited to 'drivers/mfd')
46 files changed, 26508 insertions, 0 deletions
diff --git a/drivers/mfd/88pm8607.c b/drivers/mfd/88pm8607.c new file mode 100644 index 00000000000..7e3f6590799 --- /dev/null +++ b/drivers/mfd/88pm8607.c @@ -0,0 +1,302 @@ +/* + * Base driver for Marvell 88PM8607 + * + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/mfd/88pm8607.h> + + +#define PM8607_REG_RESOURCE(_start, _end) \ +{ \ + .start = PM8607_##_start, \ + .end = PM8607_##_end, \ + .flags = IORESOURCE_IO, \ +} + +static struct resource pm8607_regulator_resources[] = { + PM8607_REG_RESOURCE(BUCK1, BUCK1), + PM8607_REG_RESOURCE(BUCK2, BUCK2), + PM8607_REG_RESOURCE(BUCK3, BUCK3), + PM8607_REG_RESOURCE(LDO1, LDO1), + PM8607_REG_RESOURCE(LDO2, LDO2), + PM8607_REG_RESOURCE(LDO3, LDO3), + PM8607_REG_RESOURCE(LDO4, LDO4), + PM8607_REG_RESOURCE(LDO5, LDO5), + PM8607_REG_RESOURCE(LDO6, LDO6), + PM8607_REG_RESOURCE(LDO7, LDO7), + PM8607_REG_RESOURCE(LDO8, LDO8), + PM8607_REG_RESOURCE(LDO9, LDO9), + PM8607_REG_RESOURCE(LDO10, LDO10), + PM8607_REG_RESOURCE(LDO12, LDO12), + PM8607_REG_RESOURCE(LDO14, LDO14), +}; + +#define PM8607_REG_DEVS(_name, _id) \ +{ \ + .name = "88pm8607-" #_name, \ + .num_resources = 1, \ + .resources = &pm8607_regulator_resources[PM8607_ID_##_id], \ +} + +static struct mfd_cell pm8607_devs[] = { + PM8607_REG_DEVS(buck1, BUCK1), + PM8607_REG_DEVS(buck2, BUCK2), + PM8607_REG_DEVS(buck3, BUCK3), + PM8607_REG_DEVS(ldo1, LDO1), + PM8607_REG_DEVS(ldo2, LDO2), + PM8607_REG_DEVS(ldo3, LDO3), + PM8607_REG_DEVS(ldo4, LDO4), + PM8607_REG_DEVS(ldo5, LDO5), + PM8607_REG_DEVS(ldo6, LDO6), + PM8607_REG_DEVS(ldo7, LDO7), + PM8607_REG_DEVS(ldo8, LDO8), + PM8607_REG_DEVS(ldo9, LDO9), + PM8607_REG_DEVS(ldo10, LDO10), + PM8607_REG_DEVS(ldo12, LDO12), + PM8607_REG_DEVS(ldo14, LDO14), +}; + +static inline int pm8607_read_device(struct pm8607_chip *chip, + int reg, int bytes, void *dest) +{ + struct i2c_client *i2c = chip->client; + unsigned char data; + int ret; + + data = (unsigned char)reg; + ret = i2c_master_send(i2c, &data, 1); + if (ret < 0) + return ret; + + ret = i2c_master_recv(i2c, dest, bytes); + if (ret < 0) + return ret; + return 0; +} + +static inline int pm8607_write_device(struct pm8607_chip *chip, + int reg, int bytes, void *src) +{ + struct i2c_client *i2c = chip->client; + unsigned char buf[bytes + 1]; + int ret; + + buf[0] = (unsigned char)reg; + memcpy(&buf[1], src, bytes); + + ret = i2c_master_send(i2c, buf, bytes + 1); + if (ret < 0) + return ret; + return 0; +} + +int pm8607_reg_read(struct pm8607_chip *chip, int reg) +{ + unsigned char data; + int ret; + + mutex_lock(&chip->io_lock); + ret = chip->read(chip, reg, 1, &data); + mutex_unlock(&chip->io_lock); + + if (ret < 0) + return ret; + else + return (int)data; +} +EXPORT_SYMBOL(pm8607_reg_read); + +int pm8607_reg_write(struct pm8607_chip *chip, int reg, + unsigned char data) +{ + int ret; + + mutex_lock(&chip->io_lock); + ret = chip->write(chip, reg, 1, &data); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(pm8607_reg_write); + +int pm8607_bulk_read(struct pm8607_chip *chip, int reg, + int count, unsigned char *buf) +{ + int ret; + + mutex_lock(&chip->io_lock); + ret = chip->read(chip, reg, count, buf); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(pm8607_bulk_read); + +int pm8607_bulk_write(struct pm8607_chip *chip, int reg, + int count, unsigned char *buf) +{ + int ret; + + mutex_lock(&chip->io_lock); + ret = chip->write(chip, reg, count, buf); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(pm8607_bulk_write); + +int pm8607_set_bits(struct pm8607_chip *chip, int reg, + unsigned char mask, unsigned char data) +{ + unsigned char value; + int ret; + + mutex_lock(&chip->io_lock); + ret = chip->read(chip, reg, 1, &value); + if (ret < 0) + goto out; + value &= ~mask; + value |= data; + ret = chip->write(chip, reg, 1, &value); +out: + mutex_unlock(&chip->io_lock); + return ret; +} +EXPORT_SYMBOL(pm8607_set_bits); + + +static const struct i2c_device_id pm8607_id_table[] = { + { "88PM8607", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, pm8607_id_table); + + +static int __devinit pm8607_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pm8607_platform_data *pdata = client->dev.platform_data; + struct pm8607_chip *chip; + int i, count; + int ret; + + chip = kzalloc(sizeof(struct pm8607_chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->client = client; + chip->dev = &client->dev; + chip->read = pm8607_read_device; + chip->write = pm8607_write_device; + i2c_set_clientdata(client, chip); + + mutex_init(&chip->io_lock); + dev_set_drvdata(chip->dev, chip); + + ret = pm8607_reg_read(chip, PM8607_CHIP_ID); + if (ret < 0) { + dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret); + goto out; + } + if ((ret & CHIP_ID_MASK) == CHIP_ID) + dev_info(chip->dev, "Marvell 88PM8607 (ID: %02x) detected\n", + ret); + else { + dev_err(chip->dev, "Failed to detect Marvell 88PM8607. " + "Chip ID: %02x\n", ret); + goto out; + } + chip->chip_id = ret; + + ret = pm8607_reg_read(chip, PM8607_BUCK3); + if (ret < 0) { + dev_err(chip->dev, "Failed to read BUCK3 register: %d\n", ret); + goto out; + } + if (ret & PM8607_BUCK3_DOUBLE) + chip->buck3_double = 1; + + ret = pm8607_reg_read(chip, PM8607_MISC1); + if (ret < 0) { + dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret); + goto out; + } + if (pdata->i2c_port == PI2C_PORT) + ret |= PM8607_MISC1_PI2C; + else + ret &= ~PM8607_MISC1_PI2C; + ret = pm8607_reg_write(chip, PM8607_MISC1, ret); + if (ret < 0) { + dev_err(chip->dev, "Failed to write MISC1 register: %d\n", ret); + goto out; + } + + + count = ARRAY_SIZE(pm8607_devs); + for (i = 0; i < count; i++) { + ret = mfd_add_devices(chip->dev, i, &pm8607_devs[i], + 1, NULL, 0); + if (ret != 0) { + dev_err(chip->dev, "Failed to add subdevs\n"); + goto out; + } + } + + return 0; + +out: + i2c_set_clientdata(client, NULL); + kfree(chip); + return ret; +} + +static int __devexit pm8607_remove(struct i2c_client *client) +{ + struct pm8607_chip *chip = i2c_get_clientdata(client); + + mfd_remove_devices(chip->dev); + kfree(chip); + return 0; +} + +static struct i2c_driver pm8607_driver = { + .driver = { + .name = "88PM8607", + .owner = THIS_MODULE, + }, + .probe = pm8607_probe, + .remove = __devexit_p(pm8607_remove), + .id_table = pm8607_id_table, +}; + +static int __init pm8607_init(void) +{ + int ret; + ret = i2c_add_driver(&pm8607_driver); + if (ret != 0) + pr_err("Failed to register 88PM8607 I2C driver: %d\n", ret); + return ret; +} +subsys_initcall(pm8607_init); + +static void __exit pm8607_exit(void) +{ + i2c_del_driver(&pm8607_driver); +} +module_exit(pm8607_exit); + +MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM8607"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig new file mode 100644 index 00000000000..87829789243 --- /dev/null +++ b/drivers/mfd/Kconfig @@ -0,0 +1,374 @@ +# +# Multifunction miscellaneous devices +# + +menu "Multifunction device drivers" + depends on HAS_IOMEM + +config MFD_CORE + tristate + default n + +config MFD_SM501 + tristate "Support for Silicon Motion SM501" + ---help--- + This is the core driver for the Silicon Motion SM501 multimedia + companion chip. This device is a multifunction device which may + provide numerous interfaces including USB host controller, USB gadget, + asynchronous serial ports, audio functions, and a dual display video + interface. The device may be connected by PCI or local bus with + varying functions enabled. + +config MFD_SM501_GPIO + bool "Export GPIO via GPIO layer" + depends on MFD_SM501 && GPIOLIB + ---help--- + This option uses the gpio library layer to export the 64 GPIO + lines on the SM501. The platform data is used to supply the + base number for the first GPIO line to register. + +config MFD_ASIC3 + bool "Support for Compaq ASIC3" + depends on GENERIC_HARDIRQS && GPIOLIB && ARM + select MFD_CORE + ---help--- + This driver supports the ASIC3 multifunction chip found on many + PDAs (mainly iPAQ and HTC based ones) + +config MFD_SH_MOBILE_SDHI + bool "Support for SuperH Mobile SDHI" + depends on SUPERH + select MFD_CORE + ---help--- + This driver supports the SDHI hardware block found in many + SuperH Mobile SoCs. + +config MFD_DM355EVM_MSP + bool "DaVinci DM355 EVM microcontroller" + depends on I2C && MACH_DAVINCI_DM355_EVM + help + This driver supports the MSP430 microcontroller used on these + boards. MSP430 firmware manages resets and power sequencing, + inputs from buttons and the IR remote, LEDs, an RTC, and more. + +config HTC_EGPIO + bool "HTC EGPIO support" + depends on GENERIC_HARDIRQS && GPIOLIB && ARM + help + This driver supports the CPLD egpio chip present on + several HTC phones. It provides basic support for input + pins, output pins, and irqs. + +config HTC_PASIC3 + tristate "HTC PASIC3 LED/DS1WM chip support" + select MFD_CORE + help + This core driver provides register access for the LED/DS1WM + chips labeled "AIC2" and "AIC3", found on HTC Blueangel and + HTC Magician devices, respectively. Actual functionality is + handled by the leds-pasic3 and ds1wm drivers. + +config UCB1400_CORE + tristate "Philips UCB1400 Core driver" + depends on AC97_BUS + depends on GPIOLIB + help + This enables support for the Philips UCB1400 core functions. + The UCB1400 is an AC97 audio codec. + + To compile this driver as a module, choose M here: the + module will be called ucb1400_core. + +config TPS65010 + tristate "TPS6501x Power Management chips" + depends on I2C && GPIOLIB + default y if MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_OSK + help + If you say yes here you get support for the TPS6501x series of + Power Management chips. These include voltage regulators, + lithium ion/polymer battery charging, and other features that + are often used in portable devices like cell phones and cameras. + + This driver can also be built as a module. If so, the module + will be called tps65010. + +config MENELAUS + bool "Texas Instruments TWL92330/Menelaus PM chip" + depends on I2C=y && ARCH_OMAP24XX + help + If you say yes here you get support for the Texas Instruments + TWL92330/Menelaus Power Management chip. This include voltage + regulators, Dual slot memory card transceivers, real-time clock + and other features that are often used in portable devices like + cell phones and PDAs. + +config TWL4030_CORE + bool "Texas Instruments TWL4030/TWL5030/TWL6030/TPS659x0 Support" + depends on I2C=y && GENERIC_HARDIRQS + help + Say yes here if you have TWL4030 / TWL6030 family chip on your board. + This core driver provides register access and IRQ handling + facilities, and registers devices for the various functions + so that function-specific drivers can bind to them. + + These multi-function chips are found on many OMAP2 and OMAP3 + boards, providing power management, RTC, GPIO, keypad, a + high speed USB OTG transceiver, an audio codec (on most + versions) and many other features. + +config TWL4030_POWER + bool "Support power resources on TWL4030 family chips" + depends on TWL4030_CORE && ARM + help + Say yes here if you want to use the power resources on the + TWL4030 family chips. Most of these resources are regulators, + which have a separate driver; some are control signals, such + as clock request handshaking. + + This driver uses board-specific data to initialize the resources + and load scripts controling which resources are switched off/on + or reset when a sleep, wakeup or warm reset event occurs. + +config TWL4030_CODEC + bool + depends on TWL4030_CORE + select MFD_CORE + default n + +config MFD_TMIO + bool + default n + +config MFD_T7L66XB + bool "Support Toshiba T7L66XB" + depends on ARM && HAVE_CLK + select MFD_CORE + select MFD_TMIO + help + Support for Toshiba Mobile IO Controller T7L66XB + +config MFD_TC6387XB + bool "Support Toshiba TC6387XB" + depends on ARM && HAVE_CLK + select MFD_CORE + select MFD_TMIO + help + Support for Toshiba Mobile IO Controller TC6387XB + +config MFD_TC6393XB + bool "Support Toshiba TC6393XB" + depends on GPIOLIB && ARM + select MFD_CORE + select MFD_TMIO + help + Support for Toshiba Mobile IO Controller TC6393XB + +config PMIC_DA903X + bool "Dialog Semiconductor DA9030/DA9034 PMIC Support" + depends on I2C=y + help + Say yes here to support for Dialog Semiconductor DA9030 (a.k.a + ARAVA) and DA9034 (a.k.a MICCO), these are Power Management IC + usually found on PXA processors-based platforms. This includes + the I2C driver and the core APIs _only_, you have to select + individual components like LCD backlight, voltage regulators, + LEDs and battery-charger under the corresponding menus. + +config PMIC_ADP5520 + bool "Analog Devices ADP5520/01 MFD PMIC Core Support" + depends on I2C=y + help + Say yes here to add support for Analog Devices AD5520 and ADP5501, + Multifunction Power Management IC. This includes + the I2C driver and the core APIs _only_, you have to select + individual components like LCD backlight, LEDs, GPIOs and Kepad + under the corresponding menus. + +config MFD_WM8400 + tristate "Support Wolfson Microelectronics WM8400" + select MFD_CORE + depends on I2C + help + Support for the Wolfson Microelecronics WM8400 PMIC and audio + CODEC. This driver provides common support for accessing + the device, additional drivers must be enabled in order to use + the functionality of the device. + +config MFD_WM831X + bool "Support Wolfson Microelectronics WM831x/2x PMICs" + select MFD_CORE + depends on I2C=y + help + Support for the Wolfson Microelecronics WM831x and WM832x PMICs. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + +config MFD_WM8350 + tristate + +config MFD_WM8350_CONFIG_MODE_0 + bool + depends on MFD_WM8350 + +config MFD_WM8350_CONFIG_MODE_1 + bool + depends on MFD_WM8350 + +config MFD_WM8350_CONFIG_MODE_2 + bool + depends on MFD_WM8350 + +config MFD_WM8350_CONFIG_MODE_3 + bool + depends on MFD_WM8350 + +config MFD_WM8351_CONFIG_MODE_0 + bool + depends on MFD_WM8350 + +config MFD_WM8351_CONFIG_MODE_1 + bool + depends on MFD_WM8350 + +config MFD_WM8351_CONFIG_MODE_2 + bool + depends on MFD_WM8350 + +config MFD_WM8351_CONFIG_MODE_3 + bool + depends on MFD_WM8350 + +config MFD_WM8352_CONFIG_MODE_0 + bool + depends on MFD_WM8350 + +config MFD_WM8352_CONFIG_MODE_1 + bool + depends on MFD_WM8350 + +config MFD_WM8352_CONFIG_MODE_2 + bool + depends on MFD_WM8350 + +config MFD_WM8352_CONFIG_MODE_3 + bool + depends on MFD_WM8350 + +config MFD_WM8350_I2C + tristate "Support Wolfson Microelectronics WM8350 with I2C" + select MFD_WM8350 + depends on I2C + help + The WM8350 is an integrated audio and power management + subsystem with watchdog and RTC functionality for embedded + systems. This option enables core support for the WM8350 with + I2C as the control interface. Additional options must be + selected to enable support for the functionality of the chip. + +config MFD_PCF50633 + tristate "Support for NXP PCF50633" + depends on I2C + help + Say yes here if you have NXP PCF50633 chip on your board. + This core driver provides register access and IRQ handling + facilities, and registers devices for the various functions + so that function-specific drivers can bind to them. + +config MFD_MC13783 + tristate "Support Freescale MC13783" + depends on SPI_MASTER + select MFD_CORE + help + Support for the Freescale (Atlas) MC13783 PMIC and audio CODEC. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + +config PCF50633_ADC + tristate "Support for NXP PCF50633 ADC" + depends on MFD_PCF50633 + help + Say yes here if you want to include support for ADC in the + NXP PCF50633 chip. + +config PCF50633_GPIO + tristate "Support for NXP PCF50633 GPIO" + depends on MFD_PCF50633 + help + Say yes here if you want to include support GPIO for pins on + the PCF50633 chip. + +config AB3100_CORE + tristate "ST-Ericsson AB3100 Mixed Signal Circuit core functions" + depends on I2C + default y if ARCH_U300 + help + Select this to enable the AB3100 Mixed Signal IC core + functionality. This connects to a AB3100 on the I2C bus + and expose a number of symbols needed for dependent devices + to read and write registers and subscribe to events from + this multi-functional IC. This is needed to use other features + of the AB3100 such as battery-backed RTC, charging control, + LEDs, vibrator, system power and temperature, power management + and ALSA sound. + +config AB3100_OTP + tristate "ST-Ericsson AB3100 OTP functions" + depends on AB3100_CORE + default y if AB3100_CORE + help + Select this to enable the AB3100 Mixed Signal IC OTP (one-time + programmable memory) support. This exposes a sysfs file to read + out OTP values. + +config EZX_PCAP + bool "PCAP Support" + depends on GENERIC_HARDIRQS && SPI_MASTER + help + This enables the PCAP ASIC present on EZX Phones. This is + needed for MMC, TouchScreen, Sound, USB, etc.. + +config MFD_88PM8607 + bool "Support Marvell 88PM8607" + depends on I2C=y + select MFD_CORE + help + This supports for Marvell 88PM8607 Power Management IC. This includes + the I2C driver and the core APIs _only_, you have to select + individual components like voltage regulators, RTC and + battery-charger under the corresponding menus. + +config AB4500_CORE + tristate "ST-Ericsson's AB4500 Mixed Signal Power management chip" + depends on SPI + help + Select this option to enable access to AB4500 power management + chip. This connects to U8500 on the SSP/SPI bus and exports + read/write functions for the devices to get access to this chip. + This chip embeds various other multimedia funtionalities as well. + +endmenu + +menu "Multimedia Capabilities Port drivers" + depends on ARCH_SA1100 + +config MCP + tristate + +# Interface drivers +config MCP_SA11X0 + tristate "Support SA11x0 MCP interface" + depends on ARCH_SA1100 + select MCP + +# Chip drivers +config MCP_UCB1200 + tristate "Support for UCB1200 / UCB1300" + depends on MCP + +config MCP_UCB1200_TS + tristate "Touchscreen interface support" + depends on MCP_UCB1200 && INPUT + +endmenu diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile new file mode 100644 index 00000000000..e09eb4870db --- /dev/null +++ b/drivers/mfd/Makefile @@ -0,0 +1,58 @@ +# +# Makefile for multifunction miscellaneous devices +# + +obj-$(CONFIG_MFD_SM501) += sm501.o +obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o +obj-$(CONFIG_MFD_SH_MOBILE_SDHI) += sh_mobile_sdhi.o + +obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o +obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o + +obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o + +obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o +obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o tmio_core.o +obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o tmio_core.o + +obj-$(CONFIG_MFD_WM8400) += wm8400-core.o +wm831x-objs := wm831x-core.o wm831x-irq.o wm831x-otp.o +obj-$(CONFIG_MFD_WM831X) += wm831x.o +wm8350-objs := wm8350-core.o wm8350-regmap.o wm8350-gpio.o +wm8350-objs += wm8350-irq.o +obj-$(CONFIG_MFD_WM8350) += wm8350.o +obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o + +obj-$(CONFIG_TPS65010) += tps65010.o +obj-$(CONFIG_MENELAUS) += menelaus.o + +obj-$(CONFIG_TWL4030_CORE) += twl-core.o twl4030-irq.o twl6030-irq.o +obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o +obj-$(CONFIG_TWL4030_CODEC) += twl4030-codec.o + +obj-$(CONFIG_MFD_MC13783) += mc13783-core.o + +obj-$(CONFIG_MFD_CORE) += mfd-core.o + +obj-$(CONFIG_EZX_PCAP) += ezx-pcap.o + +obj-$(CONFIG_MCP) += mcp-core.o +obj-$(CONFIG_MCP_SA11X0) += mcp-sa11x0.o +obj-$(CONFIG_MCP_UCB1200) += ucb1x00-core.o +obj-$(CONFIG_MCP_UCB1200_TS) += ucb1x00-ts.o + +ifeq ($(CONFIG_SA1100_ASSABET),y) +obj-$(CONFIG_MCP_UCB1200) += ucb1x00-assabet.o +endif +obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o + +obj-$(CONFIG_PMIC_DA903X) += da903x.o + +obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o +obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o +obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o +obj-$(CONFIG_AB3100_CORE) += ab3100-core.o +obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o +obj-$(CONFIG_AB4500_CORE) += ab4500-core.o +obj-$(CONFIG_MFD_88PM8607) += 88pm8607.o +obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
\ No newline at end of file diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c new file mode 100644 index 00000000000..fd42a80e7bf --- /dev/null +++ b/drivers/mfd/ab3100-core.c @@ -0,0 +1,994 @@ +/* + * Copyright (C) 2007-2009 ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * Low-level core for exclusive access to the AB3100 IC on the I2C bus + * and some basic chip-configuration. + * Author: Linus Walleij <linus.walleij@stericsson.com> + */ + +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/notifier.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/mfd/ab3100.h> + +/* These are the only registers inside AB3100 used in this main file */ + +/* Interrupt event registers */ +#define AB3100_EVENTA1 0x21 +#define AB3100_EVENTA2 0x22 +#define AB3100_EVENTA3 0x23 + +/* AB3100 DAC converter registers */ +#define AB3100_DIS 0x00 +#define AB3100_D0C 0x01 +#define AB3100_D1C 0x02 +#define AB3100_D2C 0x03 +#define AB3100_D3C 0x04 + +/* Chip ID register */ +#define AB3100_CID 0x20 + +/* AB3100 interrupt registers */ +#define AB3100_IMRA1 0x24 +#define AB3100_IMRA2 0x25 +#define AB3100_IMRA3 0x26 +#define AB3100_IMRB1 0x2B +#define AB3100_IMRB2 0x2C +#define AB3100_IMRB3 0x2D + +/* System Power Monitoring and control registers */ +#define AB3100_MCA 0x2E +#define AB3100_MCB 0x2F + +/* SIM power up */ +#define AB3100_SUP 0x50 + +/* + * I2C communication + * + * The AB3100 is usually assigned address 0x48 (7-bit) + * The chip is defined in the platform i2c_board_data section. + */ + +u8 ab3100_get_chip_type(struct ab3100 *ab3100) +{ + u8 chip = ABUNKNOWN; + + switch (ab3100->chip_id & 0xf0) { + case 0xa0: + chip = AB3000; + break; + case 0xc0: + chip = AB3100; + break; + } + return chip; +} +EXPORT_SYMBOL(ab3100_get_chip_type); + +int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval) +{ + u8 regandval[2] = {reg, regval}; + int err; + + err = mutex_lock_interruptible(&ab3100->access_mutex); + if (err) + return err; + + /* + * A two-byte write message with the first byte containing the register + * number and the second byte containing the value to be written + * effectively sets a register in the AB3100. + */ + err = i2c_master_send(ab3100->i2c_client, regandval, 2); + if (err < 0) { + dev_err(ab3100->dev, + "write error (write register): %d\n", + err); + } else if (err != 2) { + dev_err(ab3100->dev, + "write error (write register) " + "%d bytes transferred (expected 2)\n", + err); + err = -EIO; + } else { + /* All is well */ + err = 0; + } + mutex_unlock(&ab3100->access_mutex); + return err; +} +EXPORT_SYMBOL(ab3100_set_register_interruptible); + + +/* + * The test registers exist at an I2C bus address up one + * from the ordinary base. They are not supposed to be used + * in production code, but sometimes you have to do that + * anyway. It's currently only used from this file so declare + * it static and do not export. + */ +static int ab3100_set_test_register_interruptible(struct ab3100 *ab3100, + u8 reg, u8 regval) +{ + u8 regandval[2] = {reg, regval}; + int err; + + err = mutex_lock_interruptible(&ab3100->access_mutex); + if (err) + return err; + + err = i2c_master_send(ab3100->testreg_client, regandval, 2); + if (err < 0) { + dev_err(ab3100->dev, + "write error (write test register): %d\n", + err); + } else if (err != 2) { + dev_err(ab3100->dev, + "write error (write test register) " + "%d bytes transferred (expected 2)\n", + err); + err = -EIO; + } else { + /* All is well */ + err = 0; + } + mutex_unlock(&ab3100->access_mutex); + + return err; +} + + +int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval) +{ + int err; + + err = mutex_lock_interruptible(&ab3100->access_mutex); + if (err) + return err; + + /* + * AB3100 require an I2C "stop" command between each message, else + * it will not work. The only way of achieveing this with the + * message transport layer is to send the read and write messages + * separately. + */ + err = i2c_master_send(ab3100->i2c_client, ®, 1); + if (err < 0) { + dev_err(ab3100->dev, + "write error (send register address): %d\n", + err); + goto get_reg_out_unlock; + } else if (err != 1) { + dev_err(ab3100->dev, + "write error (send register address) " + "%d bytes transferred (expected 1)\n", + err); + err = -EIO; + goto get_reg_out_unlock; + } else { + /* All is well */ + err = 0; + } + + err = i2c_master_recv(ab3100->i2c_client, regval, 1); + if (err < 0) { + dev_err(ab3100->dev, + "write error (read register): %d\n", + err); + goto get_reg_out_unlock; + } else if (err != 1) { + dev_err(ab3100->dev, + "write error (read register) " + "%d bytes transferred (expected 1)\n", + err); + err = -EIO; + goto get_reg_out_unlock; + } else { + /* All is well */ + err = 0; + } + + get_reg_out_unlock: + mutex_unlock(&ab3100->access_mutex); + return err; +} +EXPORT_SYMBOL(ab3100_get_register_interruptible); + + +int ab3100_get_register_page_interruptible(struct ab3100 *ab3100, + u8 first_reg, u8 *regvals, u8 numregs) +{ + int err; + + if (ab3100->chip_id == 0xa0 || + ab3100->chip_id == 0xa1) + /* These don't support paged reads */ + return -EIO; + + err = mutex_lock_interruptible(&ab3100->access_mutex); + if (err) + return err; + + /* + * Paged read also require an I2C "stop" command. + */ + err = i2c_master_send(ab3100->i2c_client, &first_reg, 1); + if (err < 0) { + dev_err(ab3100->dev, + "write error (send first register address): %d\n", + err); + goto get_reg_page_out_unlock; + } else if (err != 1) { + dev_err(ab3100->dev, + "write error (send first register address) " + "%d bytes transferred (expected 1)\n", + err); + err = -EIO; + goto get_reg_page_out_unlock; + } + + err = i2c_master_recv(ab3100->i2c_client, regvals, numregs); + if (err < 0) { + dev_err(ab3100->dev, + "write error (read register page): %d\n", + err); + goto get_reg_page_out_unlock; + } else if (err != numregs) { + dev_err(ab3100->dev, + "write error (read register page) " + "%d bytes transferred (expected %d)\n", + err, numregs); + err = -EIO; + goto get_reg_page_out_unlock; + } + + /* All is well */ + err = 0; + + get_reg_page_out_unlock: + mutex_unlock(&ab3100->access_mutex); + return err; +} +EXPORT_SYMBOL(ab3100_get_register_page_interruptible); + + +int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100, + u8 reg, u8 andmask, u8 ormask) +{ + u8 regandval[2] = {reg, 0}; + int err; + + err = mutex_lock_interruptible(&ab3100->access_mutex); + if (err) + return err; + + /* First read out the target register */ + err = i2c_master_send(ab3100->i2c_client, ®, 1); + if (err < 0) { + dev_err(ab3100->dev, + "write error (maskset send address): %d\n", + err); + goto get_maskset_unlock; + } else if (err != 1) { + dev_err(ab3100->dev, + "write error (maskset send address) " + "%d bytes transferred (expected 1)\n", + err); + err = -EIO; + goto get_maskset_unlock; + } + + err = i2c_master_recv(ab3100->i2c_client, ®andval[1], 1); + if (err < 0) { + dev_err(ab3100->dev, + "write error (maskset read register): %d\n", + err); + goto get_maskset_unlock; + } else if (err != 1) { + dev_err(ab3100->dev, + "write error (maskset read register) " + "%d bytes transferred (expected 1)\n", + err); + err = -EIO; + goto get_maskset_unlock; + } + + /* Modify the register */ + regandval[1] &= andmask; + regandval[1] |= ormask; + + /* Write the register */ + err = i2c_master_send(ab3100->i2c_client, regandval, 2); + if (err < 0) { + dev_err(ab3100->dev, + "write error (write register): %d\n", + err); + goto get_maskset_unlock; + } else if (err != 2) { + dev_err(ab3100->dev, + "write error (write register) " + "%d bytes transferred (expected 2)\n", + err); + err = -EIO; + goto get_maskset_unlock; + } + + /* All is well */ + err = 0; + + get_maskset_unlock: + mutex_unlock(&ab3100->access_mutex); + return err; +} +EXPORT_SYMBOL(ab3100_mask_and_set_register_interruptible); + + +/* + * Register a simple callback for handling any AB3100 events. + */ +int ab3100_event_register(struct ab3100 *ab3100, + struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&ab3100->event_subscribers, + nb); +} +EXPORT_SYMBOL(ab3100_event_register); + +/* + * Remove a previously registered callback. + */ +int ab3100_event_unregister(struct ab3100 *ab3100, + struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&ab3100->event_subscribers, + nb); +} +EXPORT_SYMBOL(ab3100_event_unregister); + + +int ab3100_event_registers_startup_state_get(struct ab3100 *ab3100, + u32 *fatevent) +{ + if (!ab3100->startup_events_read) + return -EAGAIN; /* Try again later */ + *fatevent = ab3100->startup_events; + return 0; +} +EXPORT_SYMBOL(ab3100_event_registers_startup_state_get); + +/* Interrupt handling worker */ +static void ab3100_work(struct work_struct *work) +{ + struct ab3100 *ab3100 = container_of(work, struct ab3100, work); + u8 event_regs[3]; + u32 fatevent; + int err; + + err = ab3100_get_register_page_interruptible(ab3100, AB3100_EVENTA1, + event_regs, 3); + if (err) + goto err_event_wq; + + fatevent = (event_regs[0] << 16) | + (event_regs[1] << 8) | + event_regs[2]; + + if (!ab3100->startup_events_read) { + ab3100->startup_events = fatevent; + ab3100->startup_events_read = true; + } + /* + * The notified parties will have to mask out the events + * they're interested in and react to them. They will be + * notified on all events, then they use the fatevent value + * to determine if they're interested. + */ + blocking_notifier_call_chain(&ab3100->event_subscribers, + fatevent, NULL); + + dev_dbg(ab3100->dev, + "IRQ Event: 0x%08x\n", fatevent); + + /* By now the IRQ should be acked and deasserted so enable it again */ + enable_irq(ab3100->i2c_client->irq); + return; + + err_event_wq: + dev_dbg(ab3100->dev, + "error in event workqueue\n"); + /* Enable the IRQ anyway, what choice do we have? */ + enable_irq(ab3100->i2c_client->irq); + return; +} + +static irqreturn_t ab3100_irq_handler(int irq, void *data) +{ + struct ab3100 *ab3100 = data; + /* + * Disable the IRQ and dispatch a worker to handle the + * event. Since the chip resides on I2C this is slow + * stuff and we will re-enable the interrupts once th + * worker has finished. + */ + disable_irq_nosync(irq); + schedule_work(&ab3100->work); + return IRQ_HANDLED; +} + +#ifdef CONFIG_DEBUG_FS +/* + * Some debugfs entries only exposed if we're using debug + */ +static int ab3100_registers_print(struct seq_file *s, void *p) +{ + struct ab3100 *ab3100 = s->private; + u8 value; + u8 reg; + + seq_printf(s, "AB3100 registers:\n"); + + for (reg = 0; reg < 0xff; reg++) { + ab3100_get_register_interruptible(ab3100, reg, &value); + seq_printf(s, "[0x%x]: 0x%x\n", reg, value); + } + return 0; +} + +static int ab3100_registers_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab3100_registers_print, inode->i_private); +} + +static const struct file_operations ab3100_registers_fops = { + .open = ab3100_registers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +struct ab3100_get_set_reg_priv { + struct ab3100 *ab3100; + bool mode; +}; + +static int ab3100_get_set_reg_open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t ab3100_get_set_reg(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab3100_get_set_reg_priv *priv = file->private_data; + struct ab3100 *ab3100 = priv->ab3100; + char buf[32]; + ssize_t buf_size; + int regp; + unsigned long user_reg; + int err; + int i = 0; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + /* + * The idea is here to parse a string which is either + * "0xnn" for reading a register, or "0xaa 0xbb" for + * writing 0xbb to the register 0xaa. First move past + * whitespace and then begin to parse the register. + */ + while ((i < buf_size) && (buf[i] == ' ')) + i++; + regp = i; + + /* + * Advance pointer to end of string then terminate + * the register string. This is needed to satisfy + * the strict_strtoul() function. + */ + while ((i < buf_size) && (buf[i] != ' ')) + i++; + buf[i] = '\0'; + + err = strict_strtoul(&buf[regp], 16, &user_reg); + if (err) + return err; + if (user_reg > 0xff) + return -EINVAL; + + /* Either we read or we write a register here */ + if (!priv->mode) { + /* Reading */ + u8 reg = (u8) user_reg; + u8 regvalue; + + ab3100_get_register_interruptible(ab3100, reg, ®value); + + dev_info(ab3100->dev, + "debug read AB3100 reg[0x%02x]: 0x%02x\n", + reg, regvalue); + } else { + int valp; + unsigned long user_value; + u8 reg = (u8) user_reg; + u8 value; + u8 regvalue; + + /* + * Writing, we need some value to write to + * the register so keep parsing the string + * from userspace. + */ + i++; + while ((i < buf_size) && (buf[i] == ' ')) + i++; + valp = i; + while ((i < buf_size) && (buf[i] != ' ')) + i++; + buf[i] = '\0'; + + err = strict_strtoul(&buf[valp], 16, &user_value); + if (err) + return err; + if (user_reg > 0xff) + return -EINVAL; + + value = (u8) user_value; + ab3100_set_register_interruptible(ab3100, reg, value); + ab3100_get_register_interruptible(ab3100, reg, ®value); + + dev_info(ab3100->dev, + "debug write reg[0x%02x] with 0x%02x, " + "after readback: 0x%02x\n", + reg, value, regvalue); + } + return buf_size; +} + +static const struct file_operations ab3100_get_set_reg_fops = { + .open = ab3100_get_set_reg_open_file, + .write = ab3100_get_set_reg, +}; + +static struct dentry *ab3100_dir; +static struct dentry *ab3100_reg_file; +static struct ab3100_get_set_reg_priv ab3100_get_priv; +static struct dentry *ab3100_get_reg_file; +static struct ab3100_get_set_reg_priv ab3100_set_priv; +static struct dentry *ab3100_set_reg_file; + +static void ab3100_setup_debugfs(struct ab3100 *ab3100) +{ + int err; + + ab3100_dir = debugfs_create_dir("ab3100", NULL); + if (!ab3100_dir) + goto exit_no_debugfs; + + ab3100_reg_file = debugfs_create_file("registers", + S_IRUGO, ab3100_dir, ab3100, + &ab3100_registers_fops); + if (!ab3100_reg_file) { + err = -ENOMEM; + goto exit_destroy_dir; + } + + ab3100_get_priv.ab3100 = ab3100; + ab3100_get_priv.mode = false; + ab3100_get_reg_file = debugfs_create_file("get_reg", + S_IWUGO, ab3100_dir, &ab3100_get_priv, + &ab3100_get_set_reg_fops); + if (!ab3100_get_reg_file) { + err = -ENOMEM; + goto exit_destroy_reg; + } + + ab3100_set_priv.ab3100 = ab3100; + ab3100_set_priv.mode = true; + ab3100_set_reg_file = debugfs_create_file("set_reg", + S_IWUGO, ab3100_dir, &ab3100_set_priv, + &ab3100_get_set_reg_fops); + if (!ab3100_set_reg_file) { + err = -ENOMEM; + goto exit_destroy_get_reg; + } + return; + + exit_destroy_get_reg: + debugfs_remove(ab3100_get_reg_file); + exit_destroy_reg: + debugfs_remove(ab3100_reg_file); + exit_destroy_dir: + debugfs_remove(ab3100_dir); + exit_no_debugfs: + return; +} +static inline void ab3100_remove_debugfs(void) +{ + debugfs_remove(ab3100_set_reg_file); + debugfs_remove(ab3100_get_reg_file); + debugfs_remove(ab3100_reg_file); + debugfs_remove(ab3100_dir); +} +#else +static inline void ab3100_setup_debugfs(struct ab3100 *ab3100) +{ +} +static inline void ab3100_remove_debugfs(void) +{ +} +#endif + +/* + * Basic set-up, datastructure creation/destruction and I2C interface. + * This sets up a default config in the AB3100 chip so that it + * will work as expected. + */ + +struct ab3100_init_setting { + u8 abreg; + u8 setting; +}; + +static const struct ab3100_init_setting __initconst +ab3100_init_settings[] = { + { + .abreg = AB3100_MCA, + .setting = 0x01 + }, { + .abreg = AB3100_MCB, + .setting = 0x30 + }, { + .abreg = AB3100_IMRA1, + .setting = 0x00 + }, { + .abreg = AB3100_IMRA2, + .setting = 0xFF + }, { + .abreg = AB3100_IMRA3, + .setting = 0x01 + }, { + .abreg = AB3100_IMRB1, + .setting = 0xBF + }, { + .abreg = AB3100_IMRB2, + .setting = 0xFF + }, { + .abreg = AB3100_IMRB3, + .setting = 0xFF + }, { + .abreg = AB3100_SUP, + .setting = 0x00 + }, { + .abreg = AB3100_DIS, + .setting = 0xF0 + }, { + .abreg = AB3100_D0C, + .setting = 0x00 + }, { + .abreg = AB3100_D1C, + .setting = 0x00 + }, { + .abreg = AB3100_D2C, + .setting = 0x00 + }, { + .abreg = AB3100_D3C, + .setting = 0x00 + }, +}; + +static int __init ab3100_setup(struct ab3100 *ab3100) +{ + int err = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(ab3100_init_settings); i++) { + err = ab3100_set_register_interruptible(ab3100, + ab3100_init_settings[i].abreg, + ab3100_init_settings[i].setting); + if (err) + goto exit_no_setup; + } + + /* + * Special trick to make the AB3100 use the 32kHz clock (RTC) + * bit 3 in test register 0x02 is a special, undocumented test + * register bit that only exist in AB3100 P1E + */ + if (ab3100->chip_id == 0xc4) { + dev_warn(ab3100->dev, + "AB3100 P1E variant detected, " + "forcing chip to 32KHz\n"); + err = ab3100_set_test_register_interruptible(ab3100, 0x02, 0x08); + } + + exit_no_setup: + return err; +} + +/* + * Here we define all the platform devices that appear + * as children of the AB3100. These are regular platform + * devices with the IORESOURCE_IO .start and .end set + * to correspond to the internal AB3100 register range + * mapping to the corresponding subdevice. + */ + +#define AB3100_DEVICE(devname, devid) \ +static struct platform_device ab3100_##devname##_device = { \ + .name = devid, \ + .id = -1, \ +} + +/* + * This lists all the subdevices and corresponding register + * ranges. + */ +AB3100_DEVICE(dac, "ab3100-dac"); +AB3100_DEVICE(leds, "ab3100-leds"); +AB3100_DEVICE(power, "ab3100-power"); +AB3100_DEVICE(regulators, "ab3100-regulators"); +AB3100_DEVICE(sim, "ab3100-sim"); +AB3100_DEVICE(uart, "ab3100-uart"); +AB3100_DEVICE(rtc, "ab3100-rtc"); +AB3100_DEVICE(charger, "ab3100-charger"); +AB3100_DEVICE(boost, "ab3100-boost"); +AB3100_DEVICE(adc, "ab3100-adc"); +AB3100_DEVICE(fuelgauge, "ab3100-fuelgauge"); +AB3100_DEVICE(vibrator, "ab3100-vibrator"); +AB3100_DEVICE(otp, "ab3100-otp"); +AB3100_DEVICE(codec, "ab3100-codec"); + +static struct platform_device * +ab3100_platform_devs[] = { + &ab3100_dac_device, + &ab3100_leds_device, + &ab3100_power_device, + &ab3100_regulators_device, + &ab3100_sim_device, + &ab3100_uart_device, + &ab3100_rtc_device, + &ab3100_charger_device, + &ab3100_boost_device, + &ab3100_adc_device, + &ab3100_fuelgauge_device, + &ab3100_vibrator_device, + &ab3100_otp_device, + &ab3100_codec_device, +}; + +struct ab_family_id { + u8 id; + char *name; +}; + +static const struct ab_family_id ids[] __initdata = { + /* AB3100 */ + { + .id = 0xc0, + .name = "P1A" + }, { + .id = 0xc1, + .name = "P1B" + }, { + .id = 0xc2, + .name = "P1C" + }, { + .id = 0xc3, + .name = "P1D" + }, { + .id = 0xc4, + .name = "P1E" + }, { + .id = 0xc5, + .name = "P1F/R1A" + }, { + .id = 0xc6, + .name = "P1G/R1A" + }, { + .id = 0xc7, + .name = "P2A/R2A" + }, { + .id = 0xc8, + .name = "P2B/R2B" + }, + /* AB3000 variants, not supported */ + { + .id = 0xa0 + }, { + .id = 0xa1 + }, { + .id = 0xa2 + }, { + .id = 0xa3 + }, { + .id = 0xa4 + }, { + .id = 0xa5 + }, { + .id = 0xa6 + }, { + .id = 0xa7 + }, + /* Terminator */ + { + .id = 0x00, + }, +}; + +static int __init ab3100_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ab3100 *ab3100; + struct ab3100_platform_data *ab3100_plf_data = + client->dev.platform_data; + int err; + int i; + + ab3100 = kzalloc(sizeof(struct ab3100), GFP_KERNEL); + if (!ab3100) { + dev_err(&client->dev, "could not allocate AB3100 device\n"); + return -ENOMEM; + } + + /* Initialize data structure */ + mutex_init(&ab3100->access_mutex); + BLOCKING_INIT_NOTIFIER_HEAD(&ab3100->event_subscribers); + + ab3100->i2c_client = client; + ab3100->dev = &ab3100->i2c_client->dev; + + i2c_set_clientdata(client, ab3100); + + /* Read chip ID register */ + err = ab3100_get_register_interruptible(ab3100, AB3100_CID, + &ab3100->chip_id); + if (err) { + dev_err(&client->dev, + "could not communicate with the AB3100 analog " + "baseband chip\n"); + goto exit_no_detect; + } + + for (i = 0; ids[i].id != 0x0; i++) { + if (ids[i].id == ab3100->chip_id) { + if (ids[i].name != NULL) { + snprintf(&ab3100->chip_name[0], + sizeof(ab3100->chip_name) - 1, + "AB3100 %s", + ids[i].name); + break; + } else { + dev_err(&client->dev, + "AB3000 is not supported\n"); + goto exit_no_detect; + } + } + } + + if (ids[i].id == 0x0) { + dev_err(&client->dev, "unknown analog baseband chip id: 0x%x\n", + ab3100->chip_id); + dev_err(&client->dev, "accepting it anyway. Please update " + "the driver.\n"); + goto exit_no_detect; + } + + dev_info(&client->dev, "Detected chip: %s\n", + &ab3100->chip_name[0]); + + /* Attach a second dummy i2c_client to the test register address */ + ab3100->testreg_client = i2c_new_dummy(client->adapter, + client->addr + 1); + if (!ab3100->testreg_client) { + err = -ENOMEM; + goto exit_no_testreg_client; + } + + err = ab3100_setup(ab3100); + if (err) + goto exit_no_setup; + + INIT_WORK(&ab3100->work, ab3100_work); + + /* This real unpredictable IRQ is of course sampled for entropy */ + err = request_irq(client->irq, ab3100_irq_handler, + IRQF_DISABLED | IRQF_SAMPLE_RANDOM, + "AB3100 IRQ", ab3100); + if (err) + goto exit_no_irq; + + /* Set parent and a pointer back to the container in device data */ + for (i = 0; i < ARRAY_SIZE(ab3100_platform_devs); i++) { + ab3100_platform_devs[i]->dev.parent = + &client->dev; + ab3100_platform_devs[i]->dev.platform_data = + ab3100_plf_data; + platform_set_drvdata(ab3100_platform_devs[i], ab3100); + } + + /* Register the platform devices */ + platform_add_devices(ab3100_platform_devs, + ARRAY_SIZE(ab3100_platform_devs)); + + ab3100_setup_debugfs(ab3100); + + return 0; + + exit_no_irq: + exit_no_setup: + i2c_unregister_device(ab3100->testreg_client); + exit_no_testreg_client: + exit_no_detect: + kfree(ab3100); + return err; +} + +static int __exit ab3100_remove(struct i2c_client *client) +{ + struct ab3100 *ab3100 = i2c_get_clientdata(client); + int i; + + /* Unregister subdevices */ + for (i = 0; i < ARRAY_SIZE(ab3100_platform_devs); i++) + platform_device_unregister(ab3100_platform_devs[i]); + + ab3100_remove_debugfs(); + i2c_unregister_device(ab3100->testreg_client); + + /* + * At this point, all subscribers should have unregistered + * their notifiers so deactivate IRQ + */ + free_irq(client->irq, ab3100); + kfree(ab3100); + return 0; +} + +static const struct i2c_device_id ab3100_id[] = { + { "ab3100", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ab3100_id); + +static struct i2c_driver ab3100_driver = { + .driver = { + .name = "ab3100", + .owner = THIS_MODULE, + }, + .id_table = ab3100_id, + .probe = ab3100_probe, + .remove = __exit_p(ab3100_remove), +}; + +static int __init ab3100_i2c_init(void) +{ + return i2c_add_driver(&ab3100_driver); +} + +static void __exit ab3100_i2c_exit(void) +{ + i2c_del_driver(&ab3100_driver); +} + +subsys_initcall(ab3100_i2c_init); +module_exit(ab3100_i2c_exit); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); +MODULE_DESCRIPTION("AB3100 core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/ab3100-otp.c b/drivers/mfd/ab3100-otp.c new file mode 100644 index 00000000000..0499b2031a2 --- /dev/null +++ b/drivers/mfd/ab3100-otp.c @@ -0,0 +1,268 @@ +/* + * drivers/mfd/ab3100_otp.c + * + * Copyright (C) 2007-2009 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * Driver to read out OTP from the AB3100 Mixed-signal circuit + * Author: Linus Walleij <linus.walleij@stericsson.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mfd/ab3100.h> +#include <linux/debugfs.h> + +/* The OTP registers */ +#define AB3100_OTP0 0xb0 +#define AB3100_OTP1 0xb1 +#define AB3100_OTP2 0xb2 +#define AB3100_OTP3 0xb3 +#define AB3100_OTP4 0xb4 +#define AB3100_OTP5 0xb5 +#define AB3100_OTP6 0xb6 +#define AB3100_OTP7 0xb7 +#define AB3100_OTPP 0xbf + +/** + * struct ab3100_otp + * @dev containing device + * @ab3100 a pointer to the parent ab3100 device struct + * @locked whether the OTP is locked, after locking, no more bits + * can be changed but before locking it is still possible + * to change bits from 1->0. + * @freq clocking frequency for the OTP, this frequency is either + * 32768Hz or 1MHz/30 + * @paf product activation flag, indicates whether this is a real + * product (paf true) or a lab board etc (paf false) + * @imeich if this is set it is possible to override the + * IMEI number found in the tac, fac and svn fields with + * (secured) software + * @cid customer ID + * @tac type allocation code of the IMEI + * @fac final assembly code of the IMEI + * @svn software version number of the IMEI + * @debugfs a debugfs file used when dumping to file + */ +struct ab3100_otp { + struct device *dev; + struct ab3100 *ab3100; + bool locked; + u32 freq; + bool paf; + bool imeich; + u16 cid:14; + u32 tac:20; + u8 fac; + u32 svn:20; + struct dentry *debugfs; +}; + +static int __init ab3100_otp_read(struct ab3100_otp *otp) +{ + struct ab3100 *ab = otp->ab3100; + u8 otpval[8]; + u8 otpp; + int err; + + err = ab3100_get_register_interruptible(ab, AB3100_OTPP, &otpp); + if (err) { + dev_err(otp->dev, "unable to read OTPP register\n"); + return err; + } + + err = ab3100_get_register_page_interruptible(ab, AB3100_OTP0, + otpval, 8); + if (err) { + dev_err(otp->dev, "unable to read OTP register page\n"); + return err; + } + + /* Cache OTP properties, they never change by nature */ + otp->locked = (otpp & 0x80); + otp->freq = (otpp & 0x40) ? 32768 : 34100; + otp->paf = (otpval[1] & 0x80); + otp->imeich = (otpval[1] & 0x40); + otp->cid = ((otpval[1] << 8) | otpval[0]) & 0x3fff; + otp->tac = ((otpval[4] & 0x0f) << 16) | (otpval[3] << 8) | otpval[2]; + otp->fac = ((otpval[5] & 0x0f) << 4) | (otpval[4] >> 4); + otp->svn = (otpval[7] << 12) | (otpval[6] << 4) | (otpval[5] >> 4); + return 0; +} + +/* + * This is a simple debugfs human-readable file that dumps out + * the contents of the OTP. + */ +#ifdef CONFIG_DEBUGFS +static int show_otp(struct seq_file *s, void *v) +{ + struct ab3100_otp *otp = s->private; + int err; + + seq_printf(s, "OTP is %s\n", otp->locked ? "LOCKED" : "UNLOCKED"); + seq_printf(s, "OTP clock switch startup is %uHz\n", otp->freq); + seq_printf(s, "PAF is %s\n", otp->paf ? "SET" : "NOT SET"); + seq_printf(s, "IMEI is %s\n", otp->imeich ? + "CHANGEABLE" : "NOT CHANGEABLE"); + seq_printf(s, "CID: 0x%04x (decimal: %d)\n", otp->cid, otp->cid); + seq_printf(s, "IMEI: %u-%u-%u\n", otp->tac, otp->fac, otp->svn); + return 0; +} + +static int ab3100_otp_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab3100_otp_show, inode->i_private); +} + +static const struct file_operations ab3100_otp_operations = { + .open = ab3100_otp_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init ab3100_otp_init_debugfs(struct device *dev, + struct ab3100_otp *otp) +{ + otp->debugfs = debugfs_create_file("ab3100_otp", S_IFREG | S_IRUGO, + NULL, otp, + &ab3100_otp_operations); + if (!otp->debugfs) { + dev_err(dev, "AB3100 debugfs OTP file registration failed!\n"); + return err; + } +} + +static void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) +{ + debugfs_remove_file(otp->debugfs); +} +#else +/* Compile this out if debugfs not selected */ +static inline int __init ab3100_otp_init_debugfs(struct device *dev, + struct ab3100_otp *otp) +{ + return 0; +} + +static inline void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) +{ +} +#endif + +#define SHOW_AB3100_ATTR(name) \ +static ssize_t ab3100_otp_##name##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{\ + struct ab3100_otp *otp = dev_get_drvdata(dev); \ + return sprintf(buf, "%u\n", otp->name); \ +} + +SHOW_AB3100_ATTR(locked) +SHOW_AB3100_ATTR(freq) +SHOW_AB3100_ATTR(paf) +SHOW_AB3100_ATTR(imeich) +SHOW_AB3100_ATTR(cid) +SHOW_AB3100_ATTR(fac) +SHOW_AB3100_ATTR(tac) +SHOW_AB3100_ATTR(svn) + +static struct device_attribute ab3100_otp_attrs[] = { + __ATTR(locked, S_IRUGO, ab3100_otp_locked_show, NULL), + __ATTR(freq, S_IRUGO, ab3100_otp_freq_show, NULL), + __ATTR(paf, S_IRUGO, ab3100_otp_paf_show, NULL), + __ATTR(imeich, S_IRUGO, ab3100_otp_imeich_show, NULL), + __ATTR(cid, S_IRUGO, ab3100_otp_cid_show, NULL), + __ATTR(fac, S_IRUGO, ab3100_otp_fac_show, NULL), + __ATTR(tac, S_IRUGO, ab3100_otp_tac_show, NULL), + __ATTR(svn, S_IRUGO, ab3100_otp_svn_show, NULL), +}; + +static int __init ab3100_otp_probe(struct platform_device *pdev) +{ + struct ab3100_otp *otp; + int err = 0; + int i; + + otp = kzalloc(sizeof(struct ab3100_otp), GFP_KERNEL); + if (!otp) { + dev_err(&pdev->dev, "could not allocate AB3100 OTP device\n"); + return -ENOMEM; + } + otp->dev = &pdev->dev; + + /* Replace platform data coming in with a local struct */ + otp->ab3100 = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, otp); + + err = ab3100_otp_read(otp); + if (err) + return err; + + dev_info(&pdev->dev, "AB3100 OTP readout registered\n"); + + /* sysfs entries */ + for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) { + err = device_create_file(&pdev->dev, + &ab3100_otp_attrs[i]); + if (err) + goto out_no_sysfs; + } + + /* debugfs entries */ + err = ab3100_otp_init_debugfs(&pdev->dev, otp); + if (err) + goto out_no_debugfs; + + return 0; + +out_no_sysfs: + for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) + device_remove_file(&pdev->dev, + &ab3100_otp_attrs[i]); +out_no_debugfs: + kfree(otp); + return err; +} + +static int __exit ab3100_otp_remove(struct platform_device *pdev) +{ + struct ab3100_otp *otp = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) + device_remove_file(&pdev->dev, + &ab3100_otp_attrs[i]); + ab3100_otp_exit_debugfs(otp); + kfree(otp); + return 0; +} + +static struct platform_driver ab3100_otp_driver = { + .driver = { + .name = "ab3100-otp", + .owner = THIS_MODULE, + }, + .remove = __exit_p(ab3100_otp_remove), +}; + +static int __init ab3100_otp_init(void) +{ + return platform_driver_probe(&ab3100_otp_driver, + ab3100_otp_probe); +} + +static void __exit ab3100_otp_exit(void) +{ + platform_driver_unregister(&ab3100_otp_driver); +} + +module_init(ab3100_otp_init); +module_exit(ab3100_otp_exit); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); +MODULE_DESCRIPTION("AB3100 OTP Readout Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/ab4500-core.c b/drivers/mfd/ab4500-core.c new file mode 100644 index 00000000000..1c44c19e073 --- /dev/null +++ b/drivers/mfd/ab4500-core.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2009 ST-Ericsson + * + * Author: Srinidhi KASAGAR <srinidhi.kasagar@stericsson.com> + * + * 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. + * + * AB4500 is a companion power management chip used with U8500. + * On this platform, this is interfaced with SSP0 controller + * which is a ARM primecell pl022. + * + * At the moment the module just exports read/write features. + * Interrupt management to be added - TODO. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/mfd/ab4500.h> + +/* just required if probe fails, we need to + * unregister the device + */ +static struct spi_driver ab4500_driver; + +/* + * This funtion writes to any AB4500 registers using + * SPI protocol & before it writes it packs the data + * in the below 24 bit frame format + * + * *|------------------------------------| + * *| 23|22...18|17.......10|9|8|7......0| + * *| r/w bank adr data | + * * ------------------------------------ + * + * This function shouldn't be called from interrupt + * context + */ +int ab4500_write(struct ab4500 *ab4500, unsigned char block, + unsigned long addr, unsigned char data) +{ + struct spi_transfer xfer; + struct spi_message msg; + int err; + unsigned long spi_data = + block << 18 | addr << 10 | data; + + mutex_lock(&ab4500->lock); + ab4500->tx_buf[0] = spi_data; + ab4500->rx_buf[0] = 0; + + xfer.tx_buf = ab4500->tx_buf; + xfer.rx_buf = NULL; + xfer.len = sizeof(unsigned long); + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + err = spi_sync(ab4500->spi, &msg); + mutex_unlock(&ab4500->lock); + + return err; +} +EXPORT_SYMBOL(ab4500_write); + +int ab4500_read(struct ab4500 *ab4500, unsigned char block, + unsigned long addr) +{ + struct spi_transfer xfer; + struct spi_message msg; + unsigned long spi_data = + 1 << 23 | block << 18 | addr << 10; + + mutex_lock(&ab4500->lock); + ab4500->tx_buf[0] = spi_data; + ab4500->rx_buf[0] = 0; + + xfer.tx_buf = ab4500->tx_buf; + xfer.rx_buf = ab4500->rx_buf; + xfer.len = sizeof(unsigned long); + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + spi_sync(ab4500->spi, &msg); + mutex_unlock(&ab4500->lock); + + return ab4500->rx_buf[0]; +} +EXPORT_SYMBOL(ab4500_read); + +/* ref: ab3100 core */ +#define AB4500_DEVICE(devname, devid) \ +static struct platform_device ab4500_##devname##_device = { \ + .name = devid, \ + .id = -1, \ +} + +/* list of childern devices of ab4500 - all are + * not populated here - TODO + */ +AB4500_DEVICE(charger, "ab4500-charger"); +AB4500_DEVICE(audio, "ab4500-audio"); +AB4500_DEVICE(usb, "ab4500-usb"); +AB4500_DEVICE(tvout, "ab4500-tvout"); +AB4500_DEVICE(sim, "ab4500-sim"); +AB4500_DEVICE(gpadc, "ab4500-gpadc"); +AB4500_DEVICE(clkmgt, "ab4500-clkmgt"); +AB4500_DEVICE(misc, "ab4500-misc"); + +static struct platform_device *ab4500_platform_devs[] = { + &ab4500_charger_device, + &ab4500_audio_device, + &ab4500_usb_device, + &ab4500_tvout_device, + &ab4500_sim_device, + &ab4500_gpadc_device, + &ab4500_clkmgt_device, + &ab4500_misc_device, +}; + +static int __init ab4500_probe(struct spi_device *spi) +{ + struct ab4500 *ab4500; + unsigned char revision; + int err = 0; + int i; + + ab4500 = kzalloc(sizeof *ab4500, GFP_KERNEL); + if (!ab4500) { + dev_err(&spi->dev, "could not allocate AB4500\n"); + err = -ENOMEM; + goto not_detect; + } + + ab4500->spi = spi; + spi_set_drvdata(spi, ab4500); + + mutex_init(&ab4500->lock); + + /* read the revision register */ + revision = ab4500_read(ab4500, AB4500_MISC, AB4500_REV_REG); + + /* revision id 0x0 is for early drop, 0x10 is for cut1.0 */ + if (revision == 0x0 || revision == 0x10) + dev_info(&spi->dev, "Detected chip: %s, revision = %x\n", + ab4500_driver.driver.name, revision); + else { + dev_err(&spi->dev, "unknown chip: 0x%x\n", revision); + goto not_detect; + } + + for (i = 0; i < ARRAY_SIZE(ab4500_platform_devs); i++) { + ab4500_platform_devs[i]->dev.parent = + &spi->dev; + platform_set_drvdata(ab4500_platform_devs[i], ab4500); + } + + /* register the ab4500 platform devices */ + platform_add_devices(ab4500_platform_devs, + ARRAY_SIZE(ab4500_platform_devs)); + + return err; + + not_detect: + spi_unregister_driver(&ab4500_driver); + kfree(ab4500); + return err; +} + +static int __devexit ab4500_remove(struct spi_device *spi) +{ + struct ab4500 *ab4500 = + spi_get_drvdata(spi); + + kfree(ab4500); + + return 0; +} + +static struct spi_driver ab4500_driver = { + .driver = { + .name = "ab4500", + .owner = THIS_MODULE, + }, + .probe = ab4500_probe, + .remove = __devexit_p(ab4500_remove) +}; + +static int __devinit ab4500_init(void) +{ + return spi_register_driver(&ab4500_driver); +} + +static void __exit ab4500_exit(void) +{ + spi_unregister_driver(&ab4500_driver); +} + +subsys_initcall(ab4500_init); +module_exit(ab4500_exit); + +MODULE_AUTHOR("Srinidhi KASAGAR <srinidhi.kasagar@stericsson.com"); +MODULE_DESCRIPTION("AB4500 core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/adp5520.c b/drivers/mfd/adp5520.c new file mode 100644 index 00000000000..b26644772d0 --- /dev/null +++ b/drivers/mfd/adp5520.c @@ -0,0 +1,379 @@ +/* + * Base driver for Analog Devices ADP5520/ADP5501 MFD PMICs + * LCD Backlight: drivers/video/backlight/adp5520_bl + * LEDs : drivers/led/leds-adp5520 + * GPIO : drivers/gpio/adp5520-gpio (ADP5520 only) + * Keys : drivers/input/keyboard/adp5520-keys (ADP5520 only) + * + * Copyright 2009 Analog Devices Inc. + * + * Derived from da903x: + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * + * Copyright (C) 2006-2008 Marvell International Ltd. + * Eric Miao <eric.miao@marvell.com> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/err.h> +#include <linux/i2c.h> + +#include <linux/mfd/adp5520.h> + +struct adp5520_chip { + struct i2c_client *client; + struct device *dev; + struct mutex lock; + struct blocking_notifier_head notifier_list; + int irq; + unsigned long id; +}; + +static int __adp5520_read(struct i2c_client *client, + int reg, uint8_t *val) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "failed reading at 0x%02x\n", reg); + return ret; + } + + *val = (uint8_t)ret; + return 0; +} + +static int __adp5520_write(struct i2c_client *client, + int reg, uint8_t val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret < 0) { + dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n", + val, reg); + return ret; + } + return 0; +} + +static int __adp5520_ack_bits(struct i2c_client *client, int reg, + uint8_t bit_mask) +{ + struct adp5520_chip *chip = i2c_get_clientdata(client); + uint8_t reg_val; + int ret; + + mutex_lock(&chip->lock); + + ret = __adp5520_read(client, reg, ®_val); + + if (!ret) { + reg_val |= bit_mask; + ret = __adp5520_write(client, reg, reg_val); + } + + mutex_unlock(&chip->lock); + return ret; +} + +int adp5520_write(struct device *dev, int reg, uint8_t val) +{ + return __adp5520_write(to_i2c_client(dev), reg, val); +} +EXPORT_SYMBOL_GPL(adp5520_write); + +int adp5520_read(struct device *dev, int reg, uint8_t *val) +{ + return __adp5520_read(to_i2c_client(dev), reg, val); +} +EXPORT_SYMBOL_GPL(adp5520_read); + +int adp5520_set_bits(struct device *dev, int reg, uint8_t bit_mask) +{ + struct adp5520_chip *chip = dev_get_drvdata(dev); + uint8_t reg_val; + int ret; + + mutex_lock(&chip->lock); + + ret = __adp5520_read(chip->client, reg, ®_val); + + if (!ret && ((reg_val & bit_mask) == 0)) { + reg_val |= bit_mask; + ret = __adp5520_write(chip->client, reg, reg_val); + } + + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL_GPL(adp5520_set_bits); + +int adp5520_clr_bits(struct device *dev, int reg, uint8_t bit_mask) +{ + struct adp5520_chip *chip = dev_get_drvdata(dev); + uint8_t reg_val; + int ret; + + mutex_lock(&chip->lock); + + ret = __adp5520_read(chip->client, reg, ®_val); + + if (!ret && (reg_val & bit_mask)) { + reg_val &= ~bit_mask; + ret = __adp5520_write(chip->client, reg, reg_val); + } + + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL_GPL(adp5520_clr_bits); + +int adp5520_register_notifier(struct device *dev, struct notifier_block *nb, + unsigned int events) +{ + struct adp5520_chip *chip = dev_get_drvdata(dev); + + if (chip->irq) { + adp5520_set_bits(chip->dev, ADP5520_INTERRUPT_ENABLE, + events & (ADP5520_KP_IEN | ADP5520_KR_IEN | + ADP5520_OVP_IEN | ADP5520_CMPR_IEN)); + + return blocking_notifier_chain_register(&chip->notifier_list, + nb); + } + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(adp5520_register_notifier); + +int adp5520_unregister_notifier(struct device *dev, struct notifier_block *nb, + unsigned int events) +{ + struct adp5520_chip *chip = dev_get_drvdata(dev); + + adp5520_clr_bits(chip->dev, ADP5520_INTERRUPT_ENABLE, + events & (ADP5520_KP_IEN | ADP5520_KR_IEN | + ADP5520_OVP_IEN | ADP5520_CMPR_IEN)); + + return blocking_notifier_chain_unregister(&chip->notifier_list, nb); +} +EXPORT_SYMBOL_GPL(adp5520_unregister_notifier); + +static irqreturn_t adp5520_irq_thread(int irq, void *data) +{ + struct adp5520_chip *chip = data; + unsigned int events; + uint8_t reg_val; + int ret; + + ret = __adp5520_read(chip->client, ADP5520_MODE_STATUS, ®_val); + if (ret) + goto out; + + events = reg_val & (ADP5520_OVP_INT | ADP5520_CMPR_INT | + ADP5520_GPI_INT | ADP5520_KR_INT | ADP5520_KP_INT); + + blocking_notifier_call_chain(&chip->notifier_list, events, NULL); + /* ACK, Sticky bits are W1C */ + __adp5520_ack_bits(chip->client, ADP5520_MODE_STATUS, events); + +out: + return IRQ_HANDLED; +} + +static int __remove_subdev(struct device *dev, void *unused) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +static int adp5520_remove_subdevs(struct adp5520_chip *chip) +{ + return device_for_each_child(chip->dev, NULL, __remove_subdev); +} + +static int __devinit adp5520_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adp5520_platform_data *pdata = client->dev.platform_data; + struct platform_device *pdev; + struct adp5520_chip *chip; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, "SMBUS Word Data not Supported\n"); + return -EIO; + } + + if (pdata == NULL) { + dev_err(&client->dev, "missing platform data\n"); + return -ENODEV; + } + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + chip->client = client; + + chip->dev = &client->dev; + chip->irq = client->irq; + chip->id = id->driver_data; + mutex_init(&chip->lock); + + if (chip->irq) { + BLOCKING_INIT_NOTIFIER_HEAD(&chip->notifier_list); + + ret = request_threaded_irq(chip->irq, NULL, adp5520_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "adp5520", chip); + if (ret) { + dev_err(&client->dev, "failed to request irq %d\n", + chip->irq); + goto out_free_chip; + } + } + + ret = adp5520_write(chip->dev, ADP5520_MODE_STATUS, ADP5520_nSTNBY); + if (ret) { + dev_err(&client->dev, "failed to write\n"); + goto out_free_irq; + } + + if (pdata->keys) { + pdev = platform_device_register_data(chip->dev, "adp5520-keys", + chip->id, pdata->keys, sizeof(*pdata->keys)); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto out_remove_subdevs; + } + } + + if (pdata->gpio) { + pdev = platform_device_register_data(chip->dev, "adp5520-gpio", + chip->id, pdata->gpio, sizeof(*pdata->gpio)); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto out_remove_subdevs; + } + } + + if (pdata->leds) { + pdev = platform_device_register_data(chip->dev, "adp5520-led", + chip->id, pdata->leds, sizeof(*pdata->leds)); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto out_remove_subdevs; + } + } + + if (pdata->backlight) { + pdev = platform_device_register_data(chip->dev, + "adp5520-backlight", + chip->id, + pdata->backlight, + sizeof(*pdata->backlight)); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto out_remove_subdevs; + } + } + + return 0; + +out_remove_subdevs: + adp5520_remove_subdevs(chip); + +out_free_irq: + if (chip->irq) + free_irq(chip->irq, chip); + +out_free_chip: + i2c_set_clientdata(client, NULL); + kfree(chip); + + return ret; +} + +static int __devexit adp5520_remove(struct i2c_client *client) +{ + struct adp5520_chip *chip = dev_get_drvdata(&client->dev); + + if (chip->irq) + free_irq(chip->irq, chip); + + adp5520_remove_subdevs(chip); + adp5520_write(chip->dev, ADP5520_MODE_STATUS, 0); + i2c_set_clientdata(client, NULL); + kfree(chip); + return 0; +} + +#ifdef CONFIG_PM +static int adp5520_suspend(struct i2c_client *client, + pm_message_t state) +{ + struct adp5520_chip *chip = dev_get_drvdata(&client->dev); + + adp5520_clr_bits(chip->dev, ADP5520_MODE_STATUS, ADP5520_nSTNBY); + return 0; +} + +static int adp5520_resume(struct i2c_client *client) +{ + struct adp5520_chip *chip = dev_get_drvdata(&client->dev); + + adp5520_set_bits(chip->dev, ADP5520_MODE_STATUS, ADP5520_nSTNBY); + return 0; +} +#else +#define adp5520_suspend NULL +#define adp5520_resume NULL +#endif + +static const struct i2c_device_id adp5520_id[] = { + { "pmic-adp5520", ID_ADP5520 }, + { "pmic-adp5501", ID_ADP5501 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adp5520_id); + +static struct i2c_driver adp5520_driver = { + .driver = { + .name = "adp5520", + .owner = THIS_MODULE, + }, + .probe = adp5520_probe, + .remove = __devexit_p(adp5520_remove), + .suspend = adp5520_suspend, + .resume = adp5520_resume, + .id_table = adp5520_id, +}; + +static int __init adp5520_init(void) +{ + return i2c_add_driver(&adp5520_driver); +} +module_init(adp5520_init); + +static void __exit adp5520_exit(void) +{ + i2c_del_driver(&adp5520_driver); +} +module_exit(adp5520_exit); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("ADP5520(01) PMIC-MFD Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/asic3.c b/drivers/mfd/asic3.c new file mode 100644 index 00000000000..95c1e6bd172 --- /dev/null +++ b/drivers/mfd/asic3.c @@ -0,0 +1,975 @@ +/* + * driver/mfd/asic3.c + * + * Compaq ASIC3 support. + * + * 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. + * + * Copyright 2001 Compaq Computer Corporation. + * Copyright 2004-2005 Phil Blundell + * Copyright 2007-2008 OpenedHand Ltd. + * + * Authors: Phil Blundell <pb@handhelds.org>, + * Samuel Ortiz <sameo@openedhand.com> + * + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> + +#include <linux/mfd/asic3.h> +#include <linux/mfd/core.h> +#include <linux/mfd/ds1wm.h> +#include <linux/mfd/tmio.h> + +enum { + ASIC3_CLOCK_SPI, + ASIC3_CLOCK_OWM, + ASIC3_CLOCK_PWM0, + ASIC3_CLOCK_PWM1, + ASIC3_CLOCK_LED0, + ASIC3_CLOCK_LED1, + ASIC3_CLOCK_LED2, + ASIC3_CLOCK_SD_HOST, + ASIC3_CLOCK_SD_BUS, + ASIC3_CLOCK_SMBUS, + ASIC3_CLOCK_EX0, + ASIC3_CLOCK_EX1, +}; + +struct asic3_clk { + int enabled; + unsigned int cdex; + unsigned long rate; +}; + +#define INIT_CDEX(_name, _rate) \ + [ASIC3_CLOCK_##_name] = { \ + .cdex = CLOCK_CDEX_##_name, \ + .rate = _rate, \ + } + +struct asic3_clk asic3_clk_init[] __initdata = { + INIT_CDEX(SPI, 0), + INIT_CDEX(OWM, 5000000), + INIT_CDEX(PWM0, 0), + INIT_CDEX(PWM1, 0), + INIT_CDEX(LED0, 0), + INIT_CDEX(LED1, 0), + INIT_CDEX(LED2, 0), + INIT_CDEX(SD_HOST, 24576000), + INIT_CDEX(SD_BUS, 12288000), + INIT_CDEX(SMBUS, 0), + INIT_CDEX(EX0, 32768), + INIT_CDEX(EX1, 24576000), +}; + +struct asic3 { + void __iomem *mapping; + unsigned int bus_shift; + unsigned int irq_nr; + unsigned int irq_base; + spinlock_t lock; + u16 irq_bothedge[4]; + struct gpio_chip gpio; + struct device *dev; + void __iomem *tmio_cnf; + + struct asic3_clk clocks[ARRAY_SIZE(asic3_clk_init)]; +}; + +static int asic3_gpio_get(struct gpio_chip *chip, unsigned offset); + +static inline void asic3_write_register(struct asic3 *asic, + unsigned int reg, u32 value) +{ + iowrite16(value, asic->mapping + + (reg >> asic->bus_shift)); +} + +static inline u32 asic3_read_register(struct asic3 *asic, + unsigned int reg) +{ + return ioread16(asic->mapping + + (reg >> asic->bus_shift)); +} + +void asic3_set_register(struct asic3 *asic, u32 reg, u32 bits, bool set) +{ + unsigned long flags; + u32 val; + + spin_lock_irqsave(&asic->lock, flags); + val = asic3_read_register(asic, reg); + if (set) + val |= bits; + else + val &= ~bits; + asic3_write_register(asic, reg, val); + spin_unlock_irqrestore(&asic->lock, flags); +} + +/* IRQs */ +#define MAX_ASIC_ISR_LOOPS 20 +#define ASIC3_GPIO_BASE_INCR \ + (ASIC3_GPIO_B_BASE - ASIC3_GPIO_A_BASE) + +static void asic3_irq_flip_edge(struct asic3 *asic, + u32 base, int bit) +{ + u16 edge; + unsigned long flags; + + spin_lock_irqsave(&asic->lock, flags); + edge = asic3_read_register(asic, + base + ASIC3_GPIO_EDGE_TRIGGER); + edge ^= bit; + asic3_write_register(asic, + base + ASIC3_GPIO_EDGE_TRIGGER, edge); + spin_unlock_irqrestore(&asic->lock, flags); +} + +static void asic3_irq_demux(unsigned int irq, struct irq_desc *desc) +{ + int iter, i; + unsigned long flags; + struct asic3 *asic; + + desc->chip->ack(irq); + + asic = desc->handler_data; + + for (iter = 0 ; iter < MAX_ASIC_ISR_LOOPS; iter++) { + u32 status; + int bank; + + spin_lock_irqsave(&asic->lock, flags); + status = asic3_read_register(asic, + ASIC3_OFFSET(INTR, P_INT_STAT)); + spin_unlock_irqrestore(&asic->lock, flags); + + /* Check all ten register bits */ + if ((status & 0x3ff) == 0) + break; + + /* Handle GPIO IRQs */ + for (bank = 0; bank < ASIC3_NUM_GPIO_BANKS; bank++) { + if (status & (1 << bank)) { + unsigned long base, istat; + + base = ASIC3_GPIO_A_BASE + + bank * ASIC3_GPIO_BASE_INCR; + + spin_lock_irqsave(&asic->lock, flags); + istat = asic3_read_register(asic, + base + + ASIC3_GPIO_INT_STATUS); + /* Clearing IntStatus */ + asic3_write_register(asic, + base + + ASIC3_GPIO_INT_STATUS, 0); + spin_unlock_irqrestore(&asic->lock, flags); + + for (i = 0; i < ASIC3_GPIOS_PER_BANK; i++) { + int bit = (1 << i); + unsigned int irqnr; + + if (!(istat & bit)) + continue; + + irqnr = asic->irq_base + + (ASIC3_GPIOS_PER_BANK * bank) + + i; + desc = irq_to_desc(irqnr); + desc->handle_irq(irqnr, desc); + if (asic->irq_bothedge[bank] & bit) + asic3_irq_flip_edge(asic, base, + bit); + } + } + } + + /* Handle remaining IRQs in the status register */ + for (i = ASIC3_NUM_GPIOS; i < ASIC3_NR_IRQS; i++) { + /* They start at bit 4 and go up */ + if (status & (1 << (i - ASIC3_NUM_GPIOS + 4))) { + desc = irq_to_desc(asic->irq_base + i); + desc->handle_irq(asic->irq_base + i, + desc); + } + } + } + + if (iter >= MAX_ASIC_ISR_LOOPS) + dev_err(asic->dev, "interrupt processing overrun\n"); +} + +static inline int asic3_irq_to_bank(struct asic3 *asic, int irq) +{ + int n; + + n = (irq - asic->irq_base) >> 4; + + return (n * (ASIC3_GPIO_B_BASE - ASIC3_GPIO_A_BASE)); +} + +static inline int asic3_irq_to_index(struct asic3 *asic, int irq) +{ + return (irq - asic->irq_base) & 0xf; +} + +static void asic3_mask_gpio_irq(unsigned int irq) +{ + struct asic3 *asic = get_irq_chip_data(irq); + u32 val, bank, index; + unsigned long flags; + + bank = asic3_irq_to_bank(asic, irq); + index = asic3_irq_to_index(asic, irq); + + spin_lock_irqsave(&asic->lock, flags); + val = asic3_read_register(asic, bank + ASIC3_GPIO_MASK); + val |= 1 << index; + asic3_write_register(asic, bank + ASIC3_GPIO_MASK, val); + spin_unlock_irqrestore(&asic->lock, flags); +} + +static void asic3_mask_irq(unsigned int irq) +{ + struct asic3 *asic = get_irq_chip_data(irq); + int regval; + unsigned long flags; + + spin_lock_irqsave(&asic->lock, flags); + regval = asic3_read_register(asic, + ASIC3_INTR_BASE + + ASIC3_INTR_INT_MASK); + + regval &= ~(ASIC3_INTMASK_MASK0 << + (irq - (asic->irq_base + ASIC3_NUM_GPIOS))); + + asic3_write_register(asic, + ASIC3_INTR_BASE + + ASIC3_INTR_INT_MASK, + regval); + spin_unlock_irqrestore(&asic->lock, flags); +} + +static void asic3_unmask_gpio_irq(unsigned int irq) +{ + struct asic3 *asic = get_irq_chip_data(irq); + u32 val, bank, index; + unsigned long flags; + + bank = asic3_irq_to_bank(asic, irq); + index = asic3_irq_to_index(asic, irq); + + spin_lock_irqsave(&asic->lock, flags); + val = asic3_read_register(asic, bank + ASIC3_GPIO_MASK); + val &= ~(1 << index); + asic3_write_register(asic, bank + ASIC3_GPIO_MASK, val); + spin_unlock_irqrestore(&asic->lock, flags); +} + +static void asic3_unmask_irq(unsigned int irq) +{ + struct asic3 *asic = get_irq_chip_data(irq); + int regval; + unsigned long flags; + + spin_lock_irqsave(&asic->lock, flags); + regval = asic3_read_register(asic, + ASIC3_INTR_BASE + + ASIC3_INTR_INT_MASK); + + regval |= (ASIC3_INTMASK_MASK0 << + (irq - (asic->irq_base + ASIC3_NUM_GPIOS))); + + asic3_write_register(asic, + ASIC3_INTR_BASE + + ASIC3_INTR_INT_MASK, + regval); + spin_unlock_irqrestore(&asic->lock, flags); +} + +static int asic3_gpio_irq_type(unsigned int irq, unsigned int type) +{ + struct asic3 *asic = get_irq_chip_data(irq); + u32 bank, index; + u16 trigger, level, edge, bit; + unsigned long flags; + + bank = asic3_irq_to_bank(asic, irq); + index = asic3_irq_to_index(asic, irq); + bit = 1<<index; + + spin_lock_irqsave(&asic->lock, flags); + level = asic3_read_register(asic, + bank + ASIC3_GPIO_LEVEL_TRIGGER); + edge = asic3_read_register(asic, + bank + ASIC3_GPIO_EDGE_TRIGGER); + trigger = asic3_read_register(asic, + bank + ASIC3_GPIO_TRIGGER_TYPE); + asic->irq_bothedge[(irq - asic->irq_base) >> 4] &= ~bit; + + if (type == IRQ_TYPE_EDGE_RISING) { + trigger |= bit; + edge |= bit; + } else if (type == IRQ_TYPE_EDGE_FALLING) { + trigger |= bit; + edge &= ~bit; + } else if (type == IRQ_TYPE_EDGE_BOTH) { + trigger |= bit; + if (asic3_gpio_get(&asic->gpio, irq - asic->irq_base)) + edge &= ~bit; + else + edge |= bit; + asic->irq_bothedge[(irq - asic->irq_base) >> 4] |= bit; + } else if (type == IRQ_TYPE_LEVEL_LOW) { + trigger &= ~bit; + level &= ~bit; + } else if (type == IRQ_TYPE_LEVEL_HIGH) { + trigger &= ~bit; + level |= bit; + } else { + /* + * if type == IRQ_TYPE_NONE, we should mask interrupts, but + * be careful to not unmask them if mask was also called. + * Probably need internal state for mask. + */ + dev_notice(asic->dev, "irq type not changed\n"); + } + asic3_write_register(asic, bank + ASIC3_GPIO_LEVEL_TRIGGER, + level); + asic3_write_register(asic, bank + ASIC3_GPIO_EDGE_TRIGGER, + edge); + asic3_write_register(asic, bank + ASIC3_GPIO_TRIGGER_TYPE, + trigger); + spin_unlock_irqrestore(&asic->lock, flags); + return 0; +} + +static struct irq_chip asic3_gpio_irq_chip = { + .name = "ASIC3-GPIO", + .ack = asic3_mask_gpio_irq, + .mask = asic3_mask_gpio_irq, + .unmask = asic3_unmask_gpio_irq, + .set_type = asic3_gpio_irq_type, +}; + +static struct irq_chip asic3_irq_chip = { + .name = "ASIC3", + .ack = asic3_mask_irq, + .mask = asic3_mask_irq, + .unmask = asic3_unmask_irq, +}; + +static int __init asic3_irq_probe(struct platform_device *pdev) +{ + struct asic3 *asic = platform_get_drvdata(pdev); + unsigned long clksel = 0; + unsigned int irq, irq_base; + int ret; + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + asic->irq_nr = ret; + + /* turn on clock to IRQ controller */ + clksel |= CLOCK_SEL_CX; + asic3_write_register(asic, ASIC3_OFFSET(CLOCK, SEL), + clksel); + + irq_base = asic->irq_base; + + for (irq = irq_base; irq < irq_base + ASIC3_NR_IRQS; irq++) { + if (irq < asic->irq_base + ASIC3_NUM_GPIOS) + set_irq_chip(irq, &asic3_gpio_irq_chip); + else + set_irq_chip(irq, &asic3_irq_chip); + + set_irq_chip_data(irq, asic); + set_irq_handler(irq, handle_level_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + } + + asic3_write_register(asic, ASIC3_OFFSET(INTR, INT_MASK), + ASIC3_INTMASK_GINTMASK); + + set_irq_chained_handler(asic->irq_nr, asic3_irq_demux); + set_irq_type(asic->irq_nr, IRQ_TYPE_EDGE_RISING); + set_irq_data(asic->irq_nr, asic); + + return 0; +} + +static void asic3_irq_remove(struct platform_device *pdev) +{ + struct asic3 *asic = platform_get_drvdata(pdev); + unsigned int irq, irq_base; + + irq_base = asic->irq_base; + + for (irq = irq_base; irq < irq_base + ASIC3_NR_IRQS; irq++) { + set_irq_flags(irq, 0); + set_irq_handler(irq, NULL); + set_irq_chip(irq, NULL); + set_irq_chip_data(irq, NULL); + } + set_irq_chained_handler(asic->irq_nr, NULL); +} + +/* GPIOs */ +static int asic3_gpio_direction(struct gpio_chip *chip, + unsigned offset, int out) +{ + u32 mask = ASIC3_GPIO_TO_MASK(offset), out_reg; + unsigned int gpio_base; + unsigned long flags; + struct asic3 *asic; + + asic = container_of(chip, struct asic3, gpio); + gpio_base = ASIC3_GPIO_TO_BASE(offset); + + if (gpio_base > ASIC3_GPIO_D_BASE) { + dev_err(asic->dev, "Invalid base (0x%x) for gpio %d\n", + gpio_base, offset); + return -EINVAL; + } + + spin_lock_irqsave(&asic->lock, flags); + + out_reg = asic3_read_register(asic, gpio_base + ASIC3_GPIO_DIRECTION); + + /* Input is 0, Output is 1 */ + if (out) + out_reg |= mask; + else + out_reg &= ~mask; + + asic3_write_register(asic, gpio_base + ASIC3_GPIO_DIRECTION, out_reg); + + spin_unlock_irqrestore(&asic->lock, flags); + + return 0; + +} + +static int asic3_gpio_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + return asic3_gpio_direction(chip, offset, 0); +} + +static int asic3_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + return asic3_gpio_direction(chip, offset, 1); +} + +static int asic3_gpio_get(struct gpio_chip *chip, + unsigned offset) +{ + unsigned int gpio_base; + u32 mask = ASIC3_GPIO_TO_MASK(offset); + struct asic3 *asic; + + asic = container_of(chip, struct asic3, gpio); + gpio_base = ASIC3_GPIO_TO_BASE(offset); + + if (gpio_base > ASIC3_GPIO_D_BASE) { + dev_err(asic->dev, "Invalid base (0x%x) for gpio %d\n", + gpio_base, offset); + return -EINVAL; + } + + return asic3_read_register(asic, gpio_base + ASIC3_GPIO_STATUS) & mask; +} + +static void asic3_gpio_set(struct gpio_chip *chip, + unsigned offset, int value) +{ + u32 mask, out_reg; + unsigned int gpio_base; + unsigned long flags; + struct asic3 *asic; + + asic = container_of(chip, struct asic3, gpio); + gpio_base = ASIC3_GPIO_TO_BASE(offset); + + if (gpio_base > ASIC3_GPIO_D_BASE) { + dev_err(asic->dev, "Invalid base (0x%x) for gpio %d\n", + gpio_base, offset); + return; + } + + mask = ASIC3_GPIO_TO_MASK(offset); + + spin_lock_irqsave(&asic->lock, flags); + + out_reg = asic3_read_register(asic, gpio_base + ASIC3_GPIO_OUT); + + if (value) + out_reg |= mask; + else + out_reg &= ~mask; + + asic3_write_register(asic, gpio_base + ASIC3_GPIO_OUT, out_reg); + + spin_unlock_irqrestore(&asic->lock, flags); + + return; +} + +static __init int asic3_gpio_probe(struct platform_device *pdev, + u16 *gpio_config, int num) +{ + struct asic3 *asic = platform_get_drvdata(pdev); + u16 alt_reg[ASIC3_NUM_GPIO_BANKS]; + u16 out_reg[ASIC3_NUM_GPIO_BANKS]; + u16 dir_reg[ASIC3_NUM_GPIO_BANKS]; + int i; + + memset(alt_reg, 0, ASIC3_NUM_GPIO_BANKS * sizeof(u16)); + memset(out_reg, 0, ASIC3_NUM_GPIO_BANKS * sizeof(u16)); + memset(dir_reg, 0, ASIC3_NUM_GPIO_BANKS * sizeof(u16)); + + /* Enable all GPIOs */ + asic3_write_register(asic, ASIC3_GPIO_OFFSET(A, MASK), 0xffff); + asic3_write_register(asic, ASIC3_GPIO_OFFSET(B, MASK), 0xffff); + asic3_write_register(asic, ASIC3_GPIO_OFFSET(C, MASK), 0xffff); + asic3_write_register(asic, ASIC3_GPIO_OFFSET(D, MASK), 0xffff); + + for (i = 0; i < num; i++) { + u8 alt, pin, dir, init, bank_num, bit_num; + u16 config = gpio_config[i]; + + pin = ASIC3_CONFIG_GPIO_PIN(config); + alt = ASIC3_CONFIG_GPIO_ALT(config); + dir = ASIC3_CONFIG_GPIO_DIR(config); + init = ASIC3_CONFIG_GPIO_INIT(config); + + bank_num = ASIC3_GPIO_TO_BANK(pin); + bit_num = ASIC3_GPIO_TO_BIT(pin); + + alt_reg[bank_num] |= (alt << bit_num); + out_reg[bank_num] |= (init << bit_num); + dir_reg[bank_num] |= (dir << bit_num); + } + + for (i = 0; i < ASIC3_NUM_GPIO_BANKS; i++) { + asic3_write_register(asic, + ASIC3_BANK_TO_BASE(i) + + ASIC3_GPIO_DIRECTION, + dir_reg[i]); + asic3_write_register(asic, + ASIC3_BANK_TO_BASE(i) + ASIC3_GPIO_OUT, + out_reg[i]); + asic3_write_register(asic, + ASIC3_BANK_TO_BASE(i) + + ASIC3_GPIO_ALT_FUNCTION, + alt_reg[i]); + } + + return gpiochip_add(&asic->gpio); +} + +static int asic3_gpio_remove(struct platform_device *pdev) +{ + struct asic3 *asic = platform_get_drvdata(pdev); + + return gpiochip_remove(&asic->gpio); +} + +static int asic3_clk_enable(struct asic3 *asic, struct asic3_clk *clk) +{ + unsigned long flags; + u32 cdex; + + spin_lock_irqsave(&asic->lock, flags); + if (clk->enabled++ == 0) { + cdex = asic3_read_register(asic, ASIC3_OFFSET(CLOCK, CDEX)); + cdex |= clk->cdex; + asic3_write_register(asic, ASIC3_OFFSET(CLOCK, CDEX), cdex); + } + spin_unlock_irqrestore(&asic->lock, flags); + + return 0; +} + +static void asic3_clk_disable(struct asic3 *asic, struct asic3_clk *clk) +{ + unsigned long flags; + u32 cdex; + + WARN_ON(clk->enabled == 0); + + spin_lock_irqsave(&asic->lock, flags); + if (--clk->enabled == 0) { + cdex = asic3_read_register(asic, ASIC3_OFFSET(CLOCK, CDEX)); + cdex &= ~clk->cdex; + asic3_write_register(asic, ASIC3_OFFSET(CLOCK, CDEX), cdex); + } + spin_unlock_irqrestore(&asic->lock, flags); +} + +/* MFD cells (SPI, PWM, LED, DS1WM, MMC) */ +static struct ds1wm_driver_data ds1wm_pdata = { + .active_high = 1, +}; + +static struct resource ds1wm_resources[] = { + { + .start = ASIC3_OWM_BASE, + .end = ASIC3_OWM_BASE + 0x13, + .flags = IORESOURCE_MEM, + }, + { + .start = ASIC3_IRQ_OWM, + .start = ASIC3_IRQ_OWM, + .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE, + }, +}; + +static int ds1wm_enable(struct platform_device *pdev) +{ + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + /* Turn on external clocks and the OWM clock */ + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_EX0]); + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_EX1]); + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_OWM]); + msleep(1); + + /* Reset and enable DS1WM */ + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, RESET), + ASIC3_EXTCF_OWM_RESET, 1); + msleep(1); + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, RESET), + ASIC3_EXTCF_OWM_RESET, 0); + msleep(1); + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, SELECT), + ASIC3_EXTCF_OWM_EN, 1); + msleep(1); + + return 0; +} + +static int ds1wm_disable(struct platform_device *pdev) +{ + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, SELECT), + ASIC3_EXTCF_OWM_EN, 0); + + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_OWM]); + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_EX0]); + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_EX1]); + + return 0; +} + +static struct mfd_cell asic3_cell_ds1wm = { + .name = "ds1wm", + .enable = ds1wm_enable, + .disable = ds1wm_disable, + .driver_data = &ds1wm_pdata, + .num_resources = ARRAY_SIZE(ds1wm_resources), + .resources = ds1wm_resources, +}; + +static void asic3_mmc_pwr(struct platform_device *pdev, int state) +{ + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + tmio_core_mmc_pwr(asic->tmio_cnf, 1 - asic->bus_shift, state); +} + +static void asic3_mmc_clk_div(struct platform_device *pdev, int state) +{ + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + tmio_core_mmc_clk_div(asic->tmio_cnf, 1 - asic->bus_shift, state); +} + +static struct tmio_mmc_data asic3_mmc_data = { + .hclk = 24576000, + .set_pwr = asic3_mmc_pwr, + .set_clk_div = asic3_mmc_clk_div, +}; + +static struct resource asic3_mmc_resources[] = { + { + .start = ASIC3_SD_CTRL_BASE, + .end = ASIC3_SD_CTRL_BASE + 0x3ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +static int asic3_mmc_enable(struct platform_device *pdev) +{ + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + /* Not sure if it must be done bit by bit, but leaving as-is */ + asic3_set_register(asic, ASIC3_OFFSET(SDHWCTRL, SDCONF), + ASIC3_SDHWCTRL_LEVCD, 1); + asic3_set_register(asic, ASIC3_OFFSET(SDHWCTRL, SDCONF), + ASIC3_SDHWCTRL_LEVWP, 1); + asic3_set_register(asic, ASIC3_OFFSET(SDHWCTRL, SDCONF), + ASIC3_SDHWCTRL_SUSPEND, 0); + asic3_set_register(asic, ASIC3_OFFSET(SDHWCTRL, SDCONF), + ASIC3_SDHWCTRL_PCLR, 0); + + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_EX0]); + /* CLK32 used for card detection and for interruption detection + * when HCLK is stopped. + */ + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_EX1]); + msleep(1); + + /* HCLK 24.576 MHz, BCLK 12.288 MHz: */ + asic3_write_register(asic, ASIC3_OFFSET(CLOCK, SEL), + CLOCK_SEL_CX | CLOCK_SEL_SD_HCLK_SEL); + + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_SD_HOST]); + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_SD_BUS]); + msleep(1); + + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, SELECT), + ASIC3_EXTCF_SD_MEM_ENABLE, 1); + + /* Enable SD card slot 3.3V power supply */ + asic3_set_register(asic, ASIC3_OFFSET(SDHWCTRL, SDCONF), + ASIC3_SDHWCTRL_SDPWR, 1); + + /* ASIC3_SD_CTRL_BASE assumes 32-bit addressing, TMIO is 16-bit */ + tmio_core_mmc_enable(asic->tmio_cnf, 1 - asic->bus_shift, + ASIC3_SD_CTRL_BASE >> 1); + + return 0; +} + +static int asic3_mmc_disable(struct platform_device *pdev) +{ + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + /* Put in suspend mode */ + asic3_set_register(asic, ASIC3_OFFSET(SDHWCTRL, SDCONF), + ASIC3_SDHWCTRL_SUSPEND, 1); + + /* Disable clocks */ + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_SD_HOST]); + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_SD_BUS]); + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_EX0]); + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_EX1]); + return 0; +} + +static struct mfd_cell asic3_cell_mmc = { + .name = "tmio-mmc", + .enable = asic3_mmc_enable, + .disable = asic3_mmc_disable, + .driver_data = &asic3_mmc_data, + .num_resources = ARRAY_SIZE(asic3_mmc_resources), + .resources = asic3_mmc_resources, +}; + +static int __init asic3_mfd_probe(struct platform_device *pdev, + struct resource *mem) +{ + struct asic3 *asic = platform_get_drvdata(pdev); + struct resource *mem_sdio; + int irq, ret; + + mem_sdio = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!mem_sdio) + dev_dbg(asic->dev, "no SDIO MEM resource\n"); + + irq = platform_get_irq(pdev, 1); + if (irq < 0) + dev_dbg(asic->dev, "no SDIO IRQ resource\n"); + + /* DS1WM */ + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, SELECT), + ASIC3_EXTCF_OWM_SMB, 0); + + ds1wm_resources[0].start >>= asic->bus_shift; + ds1wm_resources[0].end >>= asic->bus_shift; + + asic3_cell_ds1wm.platform_data = &asic3_cell_ds1wm; + asic3_cell_ds1wm.data_size = sizeof(asic3_cell_ds1wm); + + /* MMC */ + asic->tmio_cnf = ioremap((ASIC3_SD_CONFIG_BASE >> asic->bus_shift) + + mem_sdio->start, 0x400 >> asic->bus_shift); + if (!asic->tmio_cnf) { + ret = -ENOMEM; + dev_dbg(asic->dev, "Couldn't ioremap SD_CONFIG\n"); + goto out; + } + asic3_mmc_resources[0].start >>= asic->bus_shift; + asic3_mmc_resources[0].end >>= asic->bus_shift; + + asic3_cell_mmc.platform_data = &asic3_cell_mmc; + asic3_cell_mmc.data_size = sizeof(asic3_cell_mmc); + + ret = mfd_add_devices(&pdev->dev, pdev->id, + &asic3_cell_ds1wm, 1, mem, asic->irq_base); + if (ret < 0) + goto out; + + if (mem_sdio && (irq >= 0)) + ret = mfd_add_devices(&pdev->dev, pdev->id, + &asic3_cell_mmc, 1, mem_sdio, irq); + + out: + return ret; +} + +static void asic3_mfd_remove(struct platform_device *pdev) +{ + struct asic3 *asic = platform_get_drvdata(pdev); + + mfd_remove_devices(&pdev->dev); + iounmap(asic->tmio_cnf); +} + +/* Core */ +static int __init asic3_probe(struct platform_device *pdev) +{ + struct asic3_platform_data *pdata = pdev->dev.platform_data; + struct asic3 *asic; + struct resource *mem; + unsigned long clksel; + int ret = 0; + + asic = kzalloc(sizeof(struct asic3), GFP_KERNEL); + if (asic == NULL) { + printk(KERN_ERR "kzalloc failed\n"); + return -ENOMEM; + } + + spin_lock_init(&asic->lock); + platform_set_drvdata(pdev, asic); + asic->dev = &pdev->dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + ret = -ENOMEM; + dev_err(asic->dev, "no MEM resource\n"); + goto out_free; + } + + asic->mapping = ioremap(mem->start, resource_size(mem)); + if (!asic->mapping) { + ret = -ENOMEM; + dev_err(asic->dev, "Couldn't ioremap\n"); + goto out_free; + } + + asic->irq_base = pdata->irq_base; + + /* calculate bus shift from mem resource */ + asic->bus_shift = 2 - (resource_size(mem) >> 12); + + clksel = 0; + asic3_write_register(asic, ASIC3_OFFSET(CLOCK, SEL), clksel); + + ret = asic3_irq_probe(pdev); + if (ret < 0) { + dev_err(asic->dev, "Couldn't probe IRQs\n"); + goto out_unmap; + } + + asic->gpio.base = pdata->gpio_base; + asic->gpio.ngpio = ASIC3_NUM_GPIOS; + asic->gpio.get = asic3_gpio_get; + asic->gpio.set = asic3_gpio_set; + asic->gpio.direction_input = asic3_gpio_direction_input; + asic->gpio.direction_output = asic3_gpio_direction_output; + + ret = asic3_gpio_probe(pdev, + pdata->gpio_config, + pdata->gpio_config_num); + if (ret < 0) { + dev_err(asic->dev, "GPIO probe failed\n"); + goto out_irq; + } + + /* Making a per-device copy is only needed for the + * theoretical case of multiple ASIC3s on one board: + */ + memcpy(asic->clocks, asic3_clk_init, sizeof(asic3_clk_init)); + + asic3_mfd_probe(pdev, mem); + + dev_info(asic->dev, "ASIC3 Core driver\n"); + + return 0; + + out_irq: + asic3_irq_remove(pdev); + + out_unmap: + iounmap(asic->mapping); + + out_free: + kfree(asic); + + return ret; +} + +static int __devexit asic3_remove(struct platform_device *pdev) +{ + int ret; + struct asic3 *asic = platform_get_drvdata(pdev); + + asic3_mfd_remove(pdev); + + ret = asic3_gpio_remove(pdev); + if (ret < 0) + return ret; + asic3_irq_remove(pdev); + + asic3_write_register(asic, ASIC3_OFFSET(CLOCK, SEL), 0); + + iounmap(asic->mapping); + + kfree(asic); + + return 0; +} + +static void asic3_shutdown(struct platform_device *pdev) +{ +} + +static struct platform_driver asic3_device_driver = { + .driver = { + .name = "asic3", + }, + .remove = __devexit_p(asic3_remove), + .shutdown = asic3_shutdown, +}; + +static int __init asic3_init(void) +{ + int retval = 0; + retval = platform_driver_probe(&asic3_device_driver, asic3_probe); + return retval; +} + +subsys_initcall(asic3_init); diff --git a/drivers/mfd/da903x.c b/drivers/mfd/da903x.c new file mode 100644 index 00000000000..e5ffe561739 --- /dev/null +++ b/drivers/mfd/da903x.c @@ -0,0 +1,575 @@ +/* + * Base driver for Dialog Semiconductor DA9030/DA9034 + * + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * + * Copyright (C) 2006-2008 Marvell International Ltd. + * Eric Miao <eric.miao@marvell.com> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/mfd/da903x.h> + +#define DA9030_CHIP_ID 0x00 +#define DA9030_EVENT_A 0x01 +#define DA9030_EVENT_B 0x02 +#define DA9030_EVENT_C 0x03 +#define DA9030_STATUS 0x04 +#define DA9030_IRQ_MASK_A 0x05 +#define DA9030_IRQ_MASK_B 0x06 +#define DA9030_IRQ_MASK_C 0x07 +#define DA9030_SYS_CTRL_A 0x08 +#define DA9030_SYS_CTRL_B 0x09 +#define DA9030_FAULT_LOG 0x0a + +#define DA9034_CHIP_ID 0x00 +#define DA9034_EVENT_A 0x01 +#define DA9034_EVENT_B 0x02 +#define DA9034_EVENT_C 0x03 +#define DA9034_EVENT_D 0x04 +#define DA9034_STATUS_A 0x05 +#define DA9034_STATUS_B 0x06 +#define DA9034_IRQ_MASK_A 0x07 +#define DA9034_IRQ_MASK_B 0x08 +#define DA9034_IRQ_MASK_C 0x09 +#define DA9034_IRQ_MASK_D 0x0a +#define DA9034_SYS_CTRL_A 0x0b +#define DA9034_SYS_CTRL_B 0x0c +#define DA9034_FAULT_LOG 0x0d + +struct da903x_chip; + +struct da903x_chip_ops { + int (*init_chip)(struct da903x_chip *); + int (*unmask_events)(struct da903x_chip *, unsigned int events); + int (*mask_events)(struct da903x_chip *, unsigned int events); + int (*read_events)(struct da903x_chip *, unsigned int *events); + int (*read_status)(struct da903x_chip *, unsigned int *status); +}; + +struct da903x_chip { + struct i2c_client *client; + struct device *dev; + struct da903x_chip_ops *ops; + + int type; + uint32_t events_mask; + + struct mutex lock; + struct work_struct irq_work; + + struct blocking_notifier_head notifier_list; +}; + +static inline int __da903x_read(struct i2c_client *client, + int reg, uint8_t *val) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "failed reading at 0x%02x\n", reg); + return ret; + } + + *val = (uint8_t)ret; + return 0; +} + +static inline int __da903x_reads(struct i2c_client *client, int reg, + int len, uint8_t *val) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, reg, len, val); + if (ret < 0) { + dev_err(&client->dev, "failed reading from 0x%02x\n", reg); + return ret; + } + return 0; +} + +static inline int __da903x_write(struct i2c_client *client, + int reg, uint8_t val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret < 0) { + dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n", + val, reg); + return ret; + } + return 0; +} + +static inline int __da903x_writes(struct i2c_client *client, int reg, + int len, uint8_t *val) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, reg, len, val); + if (ret < 0) { + dev_err(&client->dev, "failed writings to 0x%02x\n", reg); + return ret; + } + return 0; +} + +int da903x_register_notifier(struct device *dev, struct notifier_block *nb, + unsigned int events) +{ + struct da903x_chip *chip = dev_get_drvdata(dev); + + chip->ops->unmask_events(chip, events); + return blocking_notifier_chain_register(&chip->notifier_list, nb); +} +EXPORT_SYMBOL_GPL(da903x_register_notifier); + +int da903x_unregister_notifier(struct device *dev, struct notifier_block *nb, + unsigned int events) +{ + struct da903x_chip *chip = dev_get_drvdata(dev); + + chip->ops->mask_events(chip, events); + return blocking_notifier_chain_unregister(&chip->notifier_list, nb); +} +EXPORT_SYMBOL_GPL(da903x_unregister_notifier); + +int da903x_write(struct device *dev, int reg, uint8_t val) +{ + return __da903x_write(to_i2c_client(dev), reg, val); +} +EXPORT_SYMBOL_GPL(da903x_write); + +int da903x_writes(struct device *dev, int reg, int len, uint8_t *val) +{ + return __da903x_writes(to_i2c_client(dev), reg, len, val); +} +EXPORT_SYMBOL_GPL(da903x_writes); + +int da903x_read(struct device *dev, int reg, uint8_t *val) +{ + return __da903x_read(to_i2c_client(dev), reg, val); +} +EXPORT_SYMBOL_GPL(da903x_read); + +int da903x_reads(struct device *dev, int reg, int len, uint8_t *val) +{ + return __da903x_reads(to_i2c_client(dev), reg, len, val); +} +EXPORT_SYMBOL_GPL(da903x_reads); + +int da903x_set_bits(struct device *dev, int reg, uint8_t bit_mask) +{ + struct da903x_chip *chip = dev_get_drvdata(dev); + uint8_t reg_val; + int ret = 0; + + mutex_lock(&chip->lock); + + ret = __da903x_read(chip->client, reg, ®_val); + if (ret) + goto out; + + if ((reg_val & bit_mask) == 0) { + reg_val |= bit_mask; + ret = __da903x_write(chip->client, reg, reg_val); + } +out: + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL_GPL(da903x_set_bits); + +int da903x_clr_bits(struct device *dev, int reg, uint8_t bit_mask) +{ + struct da903x_chip *chip = dev_get_drvdata(dev); + uint8_t reg_val; + int ret = 0; + + mutex_lock(&chip->lock); + + ret = __da903x_read(chip->client, reg, ®_val); + if (ret) + goto out; + + if (reg_val & bit_mask) { + reg_val &= ~bit_mask; + ret = __da903x_write(chip->client, reg, reg_val); + } +out: + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL_GPL(da903x_clr_bits); + +int da903x_update(struct device *dev, int reg, uint8_t val, uint8_t mask) +{ + struct da903x_chip *chip = dev_get_drvdata(dev); + uint8_t reg_val; + int ret = 0; + + mutex_lock(&chip->lock); + + ret = __da903x_read(chip->client, reg, ®_val); + if (ret) + goto out; + + if ((reg_val & mask) != val) { + reg_val = (reg_val & ~mask) | val; + ret = __da903x_write(chip->client, reg, reg_val); + } +out: + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL_GPL(da903x_update); + +int da903x_query_status(struct device *dev, unsigned int sbits) +{ + struct da903x_chip *chip = dev_get_drvdata(dev); + unsigned int status = 0; + + chip->ops->read_status(chip, &status); + return ((status & sbits) == sbits); +} +EXPORT_SYMBOL(da903x_query_status); + +static int __devinit da9030_init_chip(struct da903x_chip *chip) +{ + uint8_t chip_id; + int err; + + err = __da903x_read(chip->client, DA9030_CHIP_ID, &chip_id); + if (err) + return err; + + err = __da903x_write(chip->client, DA9030_SYS_CTRL_A, 0xE8); + if (err) + return err; + + dev_info(chip->dev, "DA9030 (CHIP ID: 0x%02x) detected\n", chip_id); + return 0; +} + +static int da9030_unmask_events(struct da903x_chip *chip, unsigned int events) +{ + uint8_t v[3]; + + chip->events_mask &= ~events; + + v[0] = (chip->events_mask & 0xff); + v[1] = (chip->events_mask >> 8) & 0xff; + v[2] = (chip->events_mask >> 16) & 0xff; + + return __da903x_writes(chip->client, DA9030_IRQ_MASK_A, 3, v); +} + +static int da9030_mask_events(struct da903x_chip *chip, unsigned int events) +{ + uint8_t v[3]; + + chip->events_mask |= events; + + v[0] = (chip->events_mask & 0xff); + v[1] = (chip->events_mask >> 8) & 0xff; + v[2] = (chip->events_mask >> 16) & 0xff; + + return __da903x_writes(chip->client, DA9030_IRQ_MASK_A, 3, v); +} + +static int da9030_read_events(struct da903x_chip *chip, unsigned int *events) +{ + uint8_t v[3] = {0, 0, 0}; + int ret; + + ret = __da903x_reads(chip->client, DA9030_EVENT_A, 3, v); + if (ret < 0) + return ret; + + *events = (v[2] << 16) | (v[1] << 8) | v[0]; + return 0; +} + +static int da9030_read_status(struct da903x_chip *chip, unsigned int *status) +{ + return __da903x_read(chip->client, DA9030_STATUS, (uint8_t *)status); +} + +static int da9034_init_chip(struct da903x_chip *chip) +{ + uint8_t chip_id; + int err; + + err = __da903x_read(chip->client, DA9034_CHIP_ID, &chip_id); + if (err) + return err; + + err = __da903x_write(chip->client, DA9034_SYS_CTRL_A, 0xE8); + if (err) + return err; + + /* avoid SRAM power off during sleep*/ + __da903x_write(chip->client, 0x10, 0x07); + __da903x_write(chip->client, 0x11, 0xff); + __da903x_write(chip->client, 0x12, 0xff); + + /* Enable the ONKEY power down functionality */ + __da903x_write(chip->client, DA9034_SYS_CTRL_B, 0x20); + __da903x_write(chip->client, DA9034_SYS_CTRL_A, 0x60); + + /* workaround to make LEDs work */ + __da903x_write(chip->client, 0x90, 0x01); + __da903x_write(chip->client, 0xB0, 0x08); + + /* make ADTV1 and SDTV1 effective */ + __da903x_write(chip->client, 0x20, 0x00); + + dev_info(chip->dev, "DA9034 (CHIP ID: 0x%02x) detected\n", chip_id); + return 0; +} + +static int da9034_unmask_events(struct da903x_chip *chip, unsigned int events) +{ + uint8_t v[4]; + + chip->events_mask &= ~events; + + v[0] = (chip->events_mask & 0xff); + v[1] = (chip->events_mask >> 8) & 0xff; + v[2] = (chip->events_mask >> 16) & 0xff; + v[3] = (chip->events_mask >> 24) & 0xff; + + return __da903x_writes(chip->client, DA9034_IRQ_MASK_A, 4, v); +} + +static int da9034_mask_events(struct da903x_chip *chip, unsigned int events) +{ + uint8_t v[4]; + + chip->events_mask |= events; + + v[0] = (chip->events_mask & 0xff); + v[1] = (chip->events_mask >> 8) & 0xff; + v[2] = (chip->events_mask >> 16) & 0xff; + v[3] = (chip->events_mask >> 24) & 0xff; + + return __da903x_writes(chip->client, DA9034_IRQ_MASK_A, 4, v); +} + +static int da9034_read_events(struct da903x_chip *chip, unsigned int *events) +{ + uint8_t v[4] = {0, 0, 0, 0}; + int ret; + + ret = __da903x_reads(chip->client, DA9034_EVENT_A, 4, v); + if (ret < 0) + return ret; + + *events = (v[3] << 24) | (v[2] << 16) | (v[1] << 8) | v[0]; + return 0; +} + +static int da9034_read_status(struct da903x_chip *chip, unsigned int *status) +{ + uint8_t v[2] = {0, 0}; + int ret = 0; + + ret = __da903x_reads(chip->client, DA9034_STATUS_A, 2, v); + if (ret) + return ret; + + *status = (v[1] << 8) | v[0]; + return 0; +} + +static void da903x_irq_work(struct work_struct *work) +{ + struct da903x_chip *chip = + container_of(work, struct da903x_chip, irq_work); + unsigned int events = 0; + + while (1) { + if (chip->ops->read_events(chip, &events)) + break; + + events &= ~chip->events_mask; + if (events == 0) + break; + + blocking_notifier_call_chain( + &chip->notifier_list, events, NULL); + } + enable_irq(chip->client->irq); +} + +static irqreturn_t da903x_irq_handler(int irq, void *data) +{ + struct da903x_chip *chip = data; + + disable_irq_nosync(irq); + (void)schedule_work(&chip->irq_work); + + return IRQ_HANDLED; +} + +static struct da903x_chip_ops da903x_ops[] = { + [0] = { + .init_chip = da9030_init_chip, + .unmask_events = da9030_unmask_events, + .mask_events = da9030_mask_events, + .read_events = da9030_read_events, + .read_status = da9030_read_status, + }, + [1] = { + .init_chip = da9034_init_chip, + .unmask_events = da9034_unmask_events, + .mask_events = da9034_mask_events, + .read_events = da9034_read_events, + .read_status = da9034_read_status, + } +}; + +static const struct i2c_device_id da903x_id_table[] = { + { "da9030", 0 }, + { "da9034", 1 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, da903x_id_table); + +static int __remove_subdev(struct device *dev, void *unused) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +static int da903x_remove_subdevs(struct da903x_chip *chip) +{ + return device_for_each_child(chip->dev, NULL, __remove_subdev); +} + +static int __devinit da903x_add_subdevs(struct da903x_chip *chip, + struct da903x_platform_data *pdata) +{ + struct da903x_subdev_info *subdev; + struct platform_device *pdev; + int i, ret = 0; + + for (i = 0; i < pdata->num_subdevs; i++) { + subdev = &pdata->subdevs[i]; + + pdev = platform_device_alloc(subdev->name, subdev->id); + + pdev->dev.parent = chip->dev; + pdev->dev.platform_data = subdev->platform_data; + + ret = platform_device_add(pdev); + if (ret) + goto failed; + } + return 0; + +failed: + da903x_remove_subdevs(chip); + return ret; +} + +static int __devinit da903x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct da903x_platform_data *pdata = client->dev.platform_data; + struct da903x_chip *chip; + unsigned int tmp; + int ret; + + chip = kzalloc(sizeof(struct da903x_chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->client = client; + chip->dev = &client->dev; + chip->ops = &da903x_ops[id->driver_data]; + + mutex_init(&chip->lock); + INIT_WORK(&chip->irq_work, da903x_irq_work); + BLOCKING_INIT_NOTIFIER_HEAD(&chip->notifier_list); + + i2c_set_clientdata(client, chip); + + ret = chip->ops->init_chip(chip); + if (ret) + goto out_free_chip; + + /* mask and clear all IRQs */ + chip->events_mask = 0xffffffff; + chip->ops->mask_events(chip, chip->events_mask); + chip->ops->read_events(chip, &tmp); + + ret = request_irq(client->irq, da903x_irq_handler, + IRQF_DISABLED | IRQF_TRIGGER_FALLING, + "da903x", chip); + if (ret) { + dev_err(&client->dev, "failed to request irq %d\n", + client->irq); + goto out_free_chip; + } + + ret = da903x_add_subdevs(chip, pdata); + if (ret) + goto out_free_irq; + + return 0; + +out_free_irq: + free_irq(client->irq, chip); +out_free_chip: + i2c_set_clientdata(client, NULL); + kfree(chip); + return ret; +} + +static int __devexit da903x_remove(struct i2c_client *client) +{ + struct da903x_chip *chip = i2c_get_clientdata(client); + + da903x_remove_subdevs(chip); + kfree(chip); + return 0; +} + +static struct i2c_driver da903x_driver = { + .driver = { + .name = "da903x", + .owner = THIS_MODULE, + }, + .probe = da903x_probe, + .remove = __devexit_p(da903x_remove), + .id_table = da903x_id_table, +}; + +static int __init da903x_init(void) +{ + return i2c_add_driver(&da903x_driver); +} +subsys_initcall(da903x_init); + +static void __exit da903x_exit(void) +{ + i2c_del_driver(&da903x_driver); +} +module_exit(da903x_exit); + +MODULE_DESCRIPTION("PMIC Driver for Dialog Semiconductor DA9034"); +MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>" + "Mike Rapoport <mike@compulab.co.il>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/dm355evm_msp.c b/drivers/mfd/dm355evm_msp.c new file mode 100644 index 00000000000..3d4a861976c --- /dev/null +++ b/drivers/mfd/dm355evm_msp.c @@ -0,0 +1,437 @@ +/* + * dm355evm_msp.c - driver for MSP430 firmware on DM355EVM board + * + * Copyright (C) 2008 David Brownell + * + * 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 <linux/init.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/leds.h> +#include <linux/i2c.h> +#include <linux/i2c/dm355evm_msp.h> + + +/* + * The DM355 is a DaVinci chip with video support but no C64+ DSP. Its + * EVM board has an MSP430 programmed with firmware for various board + * support functions. This driver exposes some of them directly, and + * supports other drivers (e.g. RTC, input) for more complex access. + * + * Because this firmware is entirely board-specific, this file embeds + * knowledge that would be passed as platform_data in a generic driver. + * + * This driver was tested with firmware revision A4. + */ + +#if defined(CONFIG_INPUT_DM355EVM) || defined(CONFIG_INPUT_DM355EVM_MODULE) +#define msp_has_keyboard() true +#else +#define msp_has_keyboard() false +#endif + +#if defined(CONFIG_LEDS_GPIO) || defined(CONFIG_LEDS_GPIO_MODULE) +#define msp_has_leds() true +#else +#define msp_has_leds() false +#endif + +#if defined(CONFIG_RTC_DRV_DM355EVM) || defined(CONFIG_RTC_DRV_DM355EVM_MODULE) +#define msp_has_rtc() true +#else +#define msp_has_rtc() false +#endif + +#if defined(CONFIG_VIDEO_TVP514X) || defined(CONFIG_VIDEO_TVP514X_MODULE) +#define msp_has_tvp() true +#else +#define msp_has_tvp() false +#endif + + +/*----------------------------------------------------------------------*/ + +/* REVISIT for paranoia's sake, retry reads/writes on error */ + +static struct i2c_client *msp430; + +/** + * dm355evm_msp_write - Writes a register in dm355evm_msp + * @value: the value to be written + * @reg: register address + * + * Returns result of operation - 0 is success, else negative errno + */ +int dm355evm_msp_write(u8 value, u8 reg) +{ + return i2c_smbus_write_byte_data(msp430, reg, value); +} +EXPORT_SYMBOL(dm355evm_msp_write); + +/** + * dm355evm_msp_read - Reads a register from dm355evm_msp + * @reg: register address + * + * Returns result of operation - value, or negative errno + */ +int dm355evm_msp_read(u8 reg) +{ + return i2c_smbus_read_byte_data(msp430, reg); +} +EXPORT_SYMBOL(dm355evm_msp_read); + +/*----------------------------------------------------------------------*/ + +/* + * Many of the msp430 pins are just used as fixed-direction GPIOs. + * We could export a few more of them this way, if we wanted. + */ +#define MSP_GPIO(bit,reg) ((DM355EVM_MSP_ ## reg) << 3 | (bit)) + +static const u8 msp_gpios[] = { + /* eight leds */ + MSP_GPIO(0, LED), MSP_GPIO(1, LED), + MSP_GPIO(2, LED), MSP_GPIO(3, LED), + MSP_GPIO(4, LED), MSP_GPIO(5, LED), + MSP_GPIO(6, LED), MSP_GPIO(7, LED), + /* SW6 and the NTSC/nPAL jumper */ + MSP_GPIO(0, SWITCH1), MSP_GPIO(1, SWITCH1), + MSP_GPIO(2, SWITCH1), MSP_GPIO(3, SWITCH1), + MSP_GPIO(4, SWITCH1), + /* switches on MMC/SD sockets */ + /* + * Note: EVMDM355_ECP_VA4.pdf suggests that Bit 2 and 4 should be + * checked for card detection. However on the EVM bit 1 and 3 gives + * this status, for 0 and 1 instance respectively. The pdf also + * suggests that Bit 1 and 3 should be checked for write protection. + * However on the EVM bit 2 and 4 gives this status,for 0 and 1 + * instance respectively. + */ + MSP_GPIO(2, SDMMC), MSP_GPIO(1, SDMMC), /* mmc0 WP, nCD */ + MSP_GPIO(4, SDMMC), MSP_GPIO(3, SDMMC), /* mmc1 WP, nCD */ +}; + +#define MSP_GPIO_REG(offset) (msp_gpios[(offset)] >> 3) +#define MSP_GPIO_MASK(offset) BIT(msp_gpios[(offset)] & 0x07) + +static int msp_gpio_in(struct gpio_chip *chip, unsigned offset) +{ + switch (MSP_GPIO_REG(offset)) { + case DM355EVM_MSP_SWITCH1: + case DM355EVM_MSP_SWITCH2: + case DM355EVM_MSP_SDMMC: + return 0; + default: + return -EINVAL; + } +} + +static u8 msp_led_cache; + +static int msp_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + int reg, status; + + reg = MSP_GPIO_REG(offset); + status = dm355evm_msp_read(reg); + if (status < 0) + return status; + if (reg == DM355EVM_MSP_LED) + msp_led_cache = status; + return status & MSP_GPIO_MASK(offset); +} + +static int msp_gpio_out(struct gpio_chip *chip, unsigned offset, int value) +{ + int mask, bits; + + /* NOTE: there are some other signals that could be + * packaged as output GPIOs, but they aren't as useful + * as the LEDs ... so for now we don't. + */ + if (MSP_GPIO_REG(offset) != DM355EVM_MSP_LED) + return -EINVAL; + + mask = MSP_GPIO_MASK(offset); + bits = msp_led_cache; + + bits &= ~mask; + if (value) + bits |= mask; + msp_led_cache = bits; + + return dm355evm_msp_write(bits, DM355EVM_MSP_LED); +} + +static void msp_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + msp_gpio_out(chip, offset, value); +} + +static struct gpio_chip dm355evm_msp_gpio = { + .label = "dm355evm_msp", + .owner = THIS_MODULE, + .direction_input = msp_gpio_in, + .get = msp_gpio_get, + .direction_output = msp_gpio_out, + .set = msp_gpio_set, + .base = -EINVAL, /* dynamic assignment */ + .ngpio = ARRAY_SIZE(msp_gpios), + .can_sleep = true, +}; + +/*----------------------------------------------------------------------*/ + +static struct device *add_child(struct i2c_client *client, const char *name, + void *pdata, unsigned pdata_len, + bool can_wakeup, int irq) +{ + struct platform_device *pdev; + int status; + + pdev = platform_device_alloc(name, -1); + if (!pdev) { + dev_dbg(&client->dev, "can't alloc dev\n"); + status = -ENOMEM; + goto err; + } + + device_init_wakeup(&pdev->dev, can_wakeup); + pdev->dev.parent = &client->dev; + + if (pdata) { + status = platform_device_add_data(pdev, pdata, pdata_len); + if (status < 0) { + dev_dbg(&pdev->dev, "can't add platform_data\n"); + goto err; + } + } + + if (irq) { + struct resource r = { + .start = irq, + .flags = IORESOURCE_IRQ, + }; + + status = platform_device_add_resources(pdev, &r, 1); + if (status < 0) { + dev_dbg(&pdev->dev, "can't add irq\n"); + goto err; + } + } + + status = platform_device_add(pdev); + +err: + if (status < 0) { + platform_device_put(pdev); + dev_err(&client->dev, "can't add %s dev\n", name); + return ERR_PTR(status); + } + return &pdev->dev; +} + +static int add_children(struct i2c_client *client) +{ + static const struct { + int offset; + char *label; + } config_inputs[] = { + /* 8 == right after the LEDs */ + { 8 + 0, "sw6_1", }, + { 8 + 1, "sw6_2", }, + { 8 + 2, "sw6_3", }, + { 8 + 3, "sw6_4", }, + { 8 + 4, "NTSC/nPAL", }, + }; + + struct device *child; + int status; + int i; + + /* GPIO-ish stuff */ + dm355evm_msp_gpio.dev = &client->dev; + status = gpiochip_add(&dm355evm_msp_gpio); + if (status < 0) + return status; + + /* LED output */ + if (msp_has_leds()) { +#define GPIO_LED(l) .name = l, .active_low = true + static struct gpio_led evm_leds[] = { + { GPIO_LED("dm355evm::ds14"), + .default_trigger = "heartbeat", }, + { GPIO_LED("dm355evm::ds15"), + .default_trigger = "mmc0", }, + { GPIO_LED("dm355evm::ds16"), + /* could also be a CE-ATA drive */ + .default_trigger = "mmc1", }, + { GPIO_LED("dm355evm::ds17"), + .default_trigger = "nand-disk", }, + { GPIO_LED("dm355evm::ds18"), }, + { GPIO_LED("dm355evm::ds19"), }, + { GPIO_LED("dm355evm::ds20"), }, + { GPIO_LED("dm355evm::ds21"), }, + }; +#undef GPIO_LED + + struct gpio_led_platform_data evm_led_data = { + .num_leds = ARRAY_SIZE(evm_leds), + .leds = evm_leds, + }; + + for (i = 0; i < ARRAY_SIZE(evm_leds); i++) + evm_leds[i].gpio = i + dm355evm_msp_gpio.base; + + /* NOTE: these are the only fully programmable LEDs + * on the board, since GPIO-61/ds22 (and many signals + * going to DC7) must be used for AEMIF address lines + * unless the top 1 GB of NAND is unused... + */ + child = add_child(client, "leds-gpio", + &evm_led_data, sizeof(evm_led_data), + false, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + /* configuration inputs */ + for (i = 0; i < ARRAY_SIZE(config_inputs); i++) { + int gpio = dm355evm_msp_gpio.base + config_inputs[i].offset; + + gpio_request(gpio, config_inputs[i].label); + gpio_direction_input(gpio); + + /* make it easy for userspace to see these */ + gpio_export(gpio, false); + } + + /* MMC/SD inputs -- right after the last config input */ + if (client->dev.platform_data) { + void (*mmcsd_setup)(unsigned) = client->dev.platform_data; + + mmcsd_setup(dm355evm_msp_gpio.base + 8 + 5); + } + + /* RTC is a 32 bit counter, no alarm */ + if (msp_has_rtc()) { + child = add_child(client, "rtc-dm355evm", + NULL, 0, false, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + /* input from buttons and IR remote (uses the IRQ) */ + if (msp_has_keyboard()) { + child = add_child(client, "dm355evm_keys", + NULL, 0, true, client->irq); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + return 0; +} + +/*----------------------------------------------------------------------*/ + +static void dm355evm_command(unsigned command) +{ + int status; + + status = dm355evm_msp_write(command, DM355EVM_MSP_COMMAND); + if (status < 0) + dev_err(&msp430->dev, "command %d failure %d\n", + command, status); +} + +static void dm355evm_power_off(void) +{ + dm355evm_command(MSP_COMMAND_POWEROFF); +} + +static int dm355evm_msp_remove(struct i2c_client *client) +{ + pm_power_off = NULL; + msp430 = NULL; + return 0; +} + +static int +dm355evm_msp_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int status; + const char *video = msp_has_tvp() ? "TVP5146" : "imager"; + + if (msp430) + return -EBUSY; + msp430 = client; + + /* display revision status; doubles as sanity check */ + status = dm355evm_msp_read(DM355EVM_MSP_FIRMREV); + if (status < 0) + goto fail; + dev_info(&client->dev, "firmware v.%02X, %s as video-in\n", + status, video); + + /* mux video input: either tvp5146 or some external imager */ + status = dm355evm_msp_write(msp_has_tvp() ? 0 : MSP_VIDEO_IMAGER, + DM355EVM_MSP_VIDEO_IN); + if (status < 0) + dev_warn(&client->dev, "error %d muxing %s as video-in\n", + status, video); + + /* init LED cache, and turn off the LEDs */ + msp_led_cache = 0xff; + dm355evm_msp_write(msp_led_cache, DM355EVM_MSP_LED); + + /* export capabilities we support */ + status = add_children(client); + if (status < 0) + goto fail; + + /* PM hookup */ + pm_power_off = dm355evm_power_off; + + return 0; + +fail: + /* FIXME remove children ... */ + dm355evm_msp_remove(client); + return status; +} + +static const struct i2c_device_id dm355evm_msp_ids[] = { + { "dm355evm_msp", 0 }, + { /* end of list */ }, +}; +MODULE_DEVICE_TABLE(i2c, dm355evm_msp_ids); + +static struct i2c_driver dm355evm_msp_driver = { + .driver.name = "dm355evm_msp", + .id_table = dm355evm_msp_ids, + .probe = dm355evm_msp_probe, + .remove = dm355evm_msp_remove, +}; + +static int __init dm355evm_msp_init(void) +{ + return i2c_add_driver(&dm355evm_msp_driver); +} +subsys_initcall(dm355evm_msp_init); + +static void __exit dm355evm_msp_exit(void) +{ + i2c_del_driver(&dm355evm_msp_driver); +} +module_exit(dm355evm_msp_exit); + +MODULE_DESCRIPTION("Interface to MSP430 firmware on DM355EVM"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/ezx-pcap.c b/drivers/mfd/ezx-pcap.c new file mode 100644 index 00000000000..df405af968f --- /dev/null +++ b/drivers/mfd/ezx-pcap.c @@ -0,0 +1,550 @@ +/* + * Driver for Motorola PCAP2 as present in EZX phones + * + * Copyright (C) 2006 Harald Welte <laforge@openezx.org> + * Copyright (C) 2009 Daniel Ribeiro <drwyrm@gmail.com> + * + * 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 <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mfd/ezx-pcap.h> +#include <linux/spi/spi.h> +#include <linux/gpio.h> + +#define PCAP_ADC_MAXQ 8 +struct pcap_adc_request { + u8 bank; + u8 ch[2]; + u32 flags; + void (*callback)(void *, u16[]); + void *data; +}; + +struct pcap_adc_sync_request { + u16 res[2]; + struct completion completion; +}; + +struct pcap_chip { + struct spi_device *spi; + + /* IO */ + u32 buf; + struct mutex io_mutex; + + /* IRQ */ + unsigned int irq_base; + u32 msr; + struct work_struct isr_work; + struct work_struct msr_work; + struct workqueue_struct *workqueue; + + /* ADC */ + struct pcap_adc_request *adc_queue[PCAP_ADC_MAXQ]; + u8 adc_head; + u8 adc_tail; + struct mutex adc_mutex; +}; + +/* IO */ +static int ezx_pcap_putget(struct pcap_chip *pcap, u32 *data) +{ + struct spi_transfer t; + struct spi_message m; + int status; + + memset(&t, 0, sizeof t); + spi_message_init(&m); + t.len = sizeof(u32); + spi_message_add_tail(&t, &m); + + pcap->buf = *data; + t.tx_buf = (u8 *) &pcap->buf; + t.rx_buf = (u8 *) &pcap->buf; + status = spi_sync(pcap->spi, &m); + + if (status == 0) + *data = pcap->buf; + + return status; +} + +int ezx_pcap_write(struct pcap_chip *pcap, u8 reg_num, u32 value) +{ + int ret; + + mutex_lock(&pcap->io_mutex); + value &= PCAP_REGISTER_VALUE_MASK; + value |= PCAP_REGISTER_WRITE_OP_BIT + | (reg_num << PCAP_REGISTER_ADDRESS_SHIFT); + ret = ezx_pcap_putget(pcap, &value); + mutex_unlock(&pcap->io_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(ezx_pcap_write); + +int ezx_pcap_read(struct pcap_chip *pcap, u8 reg_num, u32 *value) +{ + int ret; + + mutex_lock(&pcap->io_mutex); + *value = PCAP_REGISTER_READ_OP_BIT + | (reg_num << PCAP_REGISTER_ADDRESS_SHIFT); + + ret = ezx_pcap_putget(pcap, value); + mutex_unlock(&pcap->io_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(ezx_pcap_read); + +int ezx_pcap_set_bits(struct pcap_chip *pcap, u8 reg_num, u32 mask, u32 val) +{ + int ret; + u32 tmp = PCAP_REGISTER_READ_OP_BIT | + (reg_num << PCAP_REGISTER_ADDRESS_SHIFT); + + mutex_lock(&pcap->io_mutex); + ret = ezx_pcap_putget(pcap, &tmp); + if (ret) + goto out_unlock; + + tmp &= (PCAP_REGISTER_VALUE_MASK & ~mask); + tmp |= (val & mask) | PCAP_REGISTER_WRITE_OP_BIT | + (reg_num << PCAP_REGISTER_ADDRESS_SHIFT); + + ret = ezx_pcap_putget(pcap, &tmp); +out_unlock: + mutex_unlock(&pcap->io_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(ezx_pcap_set_bits); + +/* IRQ */ +int irq_to_pcap(struct pcap_chip *pcap, int irq) +{ + return irq - pcap->irq_base; +} +EXPORT_SYMBOL_GPL(irq_to_pcap); + +int pcap_to_irq(struct pcap_chip *pcap, int irq) +{ + return pcap->irq_base + irq; +} +EXPORT_SYMBOL_GPL(pcap_to_irq); + +static void pcap_mask_irq(unsigned int irq) +{ + struct pcap_chip *pcap = get_irq_chip_data(irq); + + pcap->msr |= 1 << irq_to_pcap(pcap, irq); + queue_work(pcap->workqueue, &pcap->msr_work); +} + +static void pcap_unmask_irq(unsigned int irq) +{ + struct pcap_chip *pcap = get_irq_chip_data(irq); + + pcap->msr &= ~(1 << irq_to_pcap(pcap, irq)); + queue_work(pcap->workqueue, &pcap->msr_work); +} + +static struct irq_chip pcap_irq_chip = { + .name = "pcap", + .mask = pcap_mask_irq, + .unmask = pcap_unmask_irq, +}; + +static void pcap_msr_work(struct work_struct *work) +{ + struct pcap_chip *pcap = container_of(work, struct pcap_chip, msr_work); + + ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr); +} + +static void pcap_isr_work(struct work_struct *work) +{ + struct pcap_chip *pcap = container_of(work, struct pcap_chip, isr_work); + struct pcap_platform_data *pdata = pcap->spi->dev.platform_data; + u32 msr, isr, int_sel, service; + int irq; + + do { + ezx_pcap_read(pcap, PCAP_REG_MSR, &msr); + ezx_pcap_read(pcap, PCAP_REG_ISR, &isr); + + /* We cant service/ack irqs that are assigned to port 2 */ + if (!(pdata->config & PCAP_SECOND_PORT)) { + ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel); + isr &= ~int_sel; + } + + ezx_pcap_write(pcap, PCAP_REG_MSR, isr | msr); + ezx_pcap_write(pcap, PCAP_REG_ISR, isr); + + local_irq_disable(); + service = isr & ~msr; + for (irq = pcap->irq_base; service; service >>= 1, irq++) { + if (service & 1) { + struct irq_desc *desc = irq_to_desc(irq); + + if (WARN(!desc, KERN_WARNING + "Invalid PCAP IRQ %d\n", irq)) + break; + + if (desc->status & IRQ_DISABLED) + note_interrupt(irq, desc, IRQ_NONE); + else + desc->handle_irq(irq, desc); + } + } + local_irq_enable(); + ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr); + } while (gpio_get_value(irq_to_gpio(pcap->spi->irq))); +} + +static void pcap_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct pcap_chip *pcap = get_irq_data(irq); + + desc->chip->ack(irq); + queue_work(pcap->workqueue, &pcap->isr_work); + return; +} + +/* ADC */ +void pcap_set_ts_bits(struct pcap_chip *pcap, u32 bits) +{ + u32 tmp; + + mutex_lock(&pcap->adc_mutex); + ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp); + tmp &= ~(PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR); + tmp |= bits & (PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR); + ezx_pcap_write(pcap, PCAP_REG_ADC, tmp); + mutex_unlock(&pcap->adc_mutex); +} +EXPORT_SYMBOL_GPL(pcap_set_ts_bits); + +static void pcap_disable_adc(struct pcap_chip *pcap) +{ + u32 tmp; + + ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp); + tmp &= ~(PCAP_ADC_ADEN|PCAP_ADC_BATT_I_ADC|PCAP_ADC_BATT_I_POLARITY); + ezx_pcap_write(pcap, PCAP_REG_ADC, tmp); +} + +static void pcap_adc_trigger(struct pcap_chip *pcap) +{ + u32 tmp; + u8 head; + + mutex_lock(&pcap->adc_mutex); + head = pcap->adc_head; + if (!pcap->adc_queue[head]) { + /* queue is empty, save power */ + pcap_disable_adc(pcap); + mutex_unlock(&pcap->adc_mutex); + return; + } + /* start conversion on requested bank, save TS_M bits */ + ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp); + tmp &= (PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR); + tmp |= pcap->adc_queue[head]->flags | PCAP_ADC_ADEN; + + if (pcap->adc_queue[head]->bank == PCAP_ADC_BANK_1) + tmp |= PCAP_ADC_AD_SEL1; + + ezx_pcap_write(pcap, PCAP_REG_ADC, tmp); + mutex_unlock(&pcap->adc_mutex); + ezx_pcap_write(pcap, PCAP_REG_ADR, PCAP_ADR_ASC); +} + +static irqreturn_t pcap_adc_irq(int irq, void *_pcap) +{ + struct pcap_chip *pcap = _pcap; + struct pcap_adc_request *req; + u16 res[2]; + u32 tmp; + + mutex_lock(&pcap->adc_mutex); + req = pcap->adc_queue[pcap->adc_head]; + + if (WARN(!req, KERN_WARNING "adc irq without pending request\n")) { + mutex_unlock(&pcap->adc_mutex); + return IRQ_HANDLED; + } + + /* read requested channels results */ + ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp); + tmp &= ~(PCAP_ADC_ADA1_MASK | PCAP_ADC_ADA2_MASK); + tmp |= (req->ch[0] << PCAP_ADC_ADA1_SHIFT); + tmp |= (req->ch[1] << PCAP_ADC_ADA2_SHIFT); + ezx_pcap_write(pcap, PCAP_REG_ADC, tmp); + ezx_pcap_read(pcap, PCAP_REG_ADR, &tmp); + res[0] = (tmp & PCAP_ADR_ADD1_MASK) >> PCAP_ADR_ADD1_SHIFT; + res[1] = (tmp & PCAP_ADR_ADD2_MASK) >> PCAP_ADR_ADD2_SHIFT; + + pcap->adc_queue[pcap->adc_head] = NULL; + pcap->adc_head = (pcap->adc_head + 1) & (PCAP_ADC_MAXQ - 1); + mutex_unlock(&pcap->adc_mutex); + + /* pass the results and release memory */ + req->callback(req->data, res); + kfree(req); + + /* trigger next conversion (if any) on queue */ + pcap_adc_trigger(pcap); + + return IRQ_HANDLED; +} + +int pcap_adc_async(struct pcap_chip *pcap, u8 bank, u32 flags, u8 ch[], + void *callback, void *data) +{ + struct pcap_adc_request *req; + + /* This will be freed after we have a result */ + req = kmalloc(sizeof(struct pcap_adc_request), GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->bank = bank; + req->flags = flags; + req->ch[0] = ch[0]; + req->ch[1] = ch[1]; + req->callback = callback; + req->data = data; + + mutex_lock(&pcap->adc_mutex); + if (pcap->adc_queue[pcap->adc_tail]) { + mutex_unlock(&pcap->adc_mutex); + kfree(req); + return -EBUSY; + } + pcap->adc_queue[pcap->adc_tail] = req; + pcap->adc_tail = (pcap->adc_tail + 1) & (PCAP_ADC_MAXQ - 1); + mutex_unlock(&pcap->adc_mutex); + + /* start conversion */ + pcap_adc_trigger(pcap); + + return 0; +} +EXPORT_SYMBOL_GPL(pcap_adc_async); + +static void pcap_adc_sync_cb(void *param, u16 res[]) +{ + struct pcap_adc_sync_request *req = param; + + req->res[0] = res[0]; + req->res[1] = res[1]; + complete(&req->completion); +} + +int pcap_adc_sync(struct pcap_chip *pcap, u8 bank, u32 flags, u8 ch[], + u16 res[]) +{ + struct pcap_adc_sync_request sync_data; + int ret; + + init_completion(&sync_data.completion); + ret = pcap_adc_async(pcap, bank, flags, ch, pcap_adc_sync_cb, + &sync_data); + if (ret) + return ret; + wait_for_completion(&sync_data.completion); + res[0] = sync_data.res[0]; + res[1] = sync_data.res[1]; + + return 0; +} +EXPORT_SYMBOL_GPL(pcap_adc_sync); + +/* subdevs */ +static int pcap_remove_subdev(struct device *dev, void *unused) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +static int __devinit pcap_add_subdev(struct pcap_chip *pcap, + struct pcap_subdev *subdev) +{ + struct platform_device *pdev; + + pdev = platform_device_alloc(subdev->name, subdev->id); + pdev->dev.parent = &pcap->spi->dev; + pdev->dev.platform_data = subdev->platform_data; + + return platform_device_add(pdev); +} + +static int __devexit ezx_pcap_remove(struct spi_device *spi) +{ + struct pcap_chip *pcap = dev_get_drvdata(&spi->dev); + struct pcap_platform_data *pdata = spi->dev.platform_data; + int i, adc_irq; + + /* remove all registered subdevs */ + device_for_each_child(&spi->dev, NULL, pcap_remove_subdev); + + /* cleanup ADC */ + adc_irq = pcap_to_irq(pcap, (pdata->config & PCAP_SECOND_PORT) ? + PCAP_IRQ_ADCDONE2 : PCAP_IRQ_ADCDONE); + free_irq(adc_irq, pcap); + mutex_lock(&pcap->adc_mutex); + for (i = 0; i < PCAP_ADC_MAXQ; i++) + kfree(pcap->adc_queue[i]); + mutex_unlock(&pcap->adc_mutex); + + /* cleanup irqchip */ + for (i = pcap->irq_base; i < (pcap->irq_base + PCAP_NIRQS); i++) + set_irq_chip_and_handler(i, NULL, NULL); + + destroy_workqueue(pcap->workqueue); + + kfree(pcap); + + return 0; +} + +static int __devinit ezx_pcap_probe(struct spi_device *spi) +{ + struct pcap_platform_data *pdata = spi->dev.platform_data; + struct pcap_chip *pcap; + int i, adc_irq; + int ret = -ENODEV; + + /* platform data is required */ + if (!pdata) + goto ret; + + pcap = kzalloc(sizeof(*pcap), GFP_KERNEL); + if (!pcap) { + ret = -ENOMEM; + goto ret; + } + + mutex_init(&pcap->io_mutex); + mutex_init(&pcap->adc_mutex); + INIT_WORK(&pcap->isr_work, pcap_isr_work); + INIT_WORK(&pcap->msr_work, pcap_msr_work); + dev_set_drvdata(&spi->dev, pcap); + + /* setup spi */ + spi->bits_per_word = 32; + spi->mode = SPI_MODE_0 | (pdata->config & PCAP_CS_AH ? SPI_CS_HIGH : 0); + ret = spi_setup(spi); + if (ret) + goto free_pcap; + + pcap->spi = spi; + + /* setup irq */ + pcap->irq_base = pdata->irq_base; + pcap->workqueue = create_singlethread_workqueue("pcapd"); + if (!pcap->workqueue) { + dev_err(&spi->dev, "cant create pcap thread\n"); + goto free_pcap; + } + + /* redirect interrupts to AP, except adcdone2 */ + if (!(pdata->config & PCAP_SECOND_PORT)) + ezx_pcap_write(pcap, PCAP_REG_INT_SEL, + (1 << PCAP_IRQ_ADCDONE2)); + + /* setup irq chip */ + for (i = pcap->irq_base; i < (pcap->irq_base + PCAP_NIRQS); i++) { + set_irq_chip_and_handler(i, &pcap_irq_chip, handle_simple_irq); + set_irq_chip_data(i, pcap); +#ifdef CONFIG_ARM + set_irq_flags(i, IRQF_VALID); +#else + set_irq_noprobe(i); +#endif + } + + /* mask/ack all PCAP interrupts */ + ezx_pcap_write(pcap, PCAP_REG_MSR, PCAP_MASK_ALL_INTERRUPT); + ezx_pcap_write(pcap, PCAP_REG_ISR, PCAP_CLEAR_INTERRUPT_REGISTER); + pcap->msr = PCAP_MASK_ALL_INTERRUPT; + + set_irq_type(spi->irq, IRQ_TYPE_EDGE_RISING); + set_irq_data(spi->irq, pcap); + set_irq_chained_handler(spi->irq, pcap_irq_handler); + set_irq_wake(spi->irq, 1); + + /* ADC */ + adc_irq = pcap_to_irq(pcap, (pdata->config & PCAP_SECOND_PORT) ? + PCAP_IRQ_ADCDONE2 : PCAP_IRQ_ADCDONE); + + ret = request_irq(adc_irq, pcap_adc_irq, 0, "ADC", pcap); + if (ret) + goto free_irqchip; + + /* setup subdevs */ + for (i = 0; i < pdata->num_subdevs; i++) { + ret = pcap_add_subdev(pcap, &pdata->subdevs[i]); + if (ret) + goto remove_subdevs; + } + + /* board specific quirks */ + if (pdata->init) + pdata->init(pcap); + + return 0; + +remove_subdevs: + device_for_each_child(&spi->dev, NULL, pcap_remove_subdev); +/* free_adc: */ + free_irq(adc_irq, pcap); +free_irqchip: + for (i = pcap->irq_base; i < (pcap->irq_base + PCAP_NIRQS); i++) + set_irq_chip_and_handler(i, NULL, NULL); +/* destroy_workqueue: */ + destroy_workqueue(pcap->workqueue); +free_pcap: + kfree(pcap); +ret: + return ret; +} + +static struct spi_driver ezxpcap_driver = { + .probe = ezx_pcap_probe, + .remove = __devexit_p(ezx_pcap_remove), + .driver = { + .name = "ezx-pcap", + .owner = THIS_MODULE, + }, +}; + +static int __init ezx_pcap_init(void) +{ + return spi_register_driver(&ezxpcap_driver); +} + +static void __exit ezx_pcap_exit(void) +{ + spi_unregister_driver(&ezxpcap_driver); +} + +subsys_initcall(ezx_pcap_init); +module_exit(ezx_pcap_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Daniel Ribeiro / Harald Welte"); +MODULE_DESCRIPTION("Motorola PCAP2 ASIC Driver"); +MODULE_ALIAS("spi:ezx-pcap"); diff --git a/drivers/mfd/htc-egpio.c b/drivers/mfd/htc-egpio.c new file mode 100644 index 00000000000..aa266e1f69b --- /dev/null +++ b/drivers/mfd/htc-egpio.c @@ -0,0 +1,442 @@ +/* + * Support for the GPIO/IRQ expander chips present on several HTC phones. + * These are implemented in CPLD chips present on the board. + * + * Copyright (c) 2007 Kevin O'Connor <kevin@koconnor.net> + * Copyright (c) 2007 Philipp Zabel <philipp.zabel@gmail.com> + * + * This file may be distributed under the terms of the GNU GPL license. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/mfd/htc-egpio.h> + +struct egpio_chip { + int reg_start; + int cached_values; + unsigned long is_out; + struct device *dev; + struct gpio_chip chip; +}; + +struct egpio_info { + spinlock_t lock; + + /* iomem info */ + void __iomem *base_addr; + int bus_shift; /* byte shift */ + int reg_shift; /* bit shift */ + int reg_mask; + + /* irq info */ + int ack_register; + int ack_write; + u16 irqs_enabled; + uint irq_start; + int nirqs; + uint chained_irq; + + /* egpio info */ + struct egpio_chip *chip; + int nchips; +}; + +static inline void egpio_writew(u16 value, struct egpio_info *ei, int reg) +{ + writew(value, ei->base_addr + (reg << ei->bus_shift)); +} + +static inline u16 egpio_readw(struct egpio_info *ei, int reg) +{ + return readw(ei->base_addr + (reg << ei->bus_shift)); +} + +/* + * IRQs + */ + +static inline void ack_irqs(struct egpio_info *ei) +{ + egpio_writew(ei->ack_write, ei, ei->ack_register); + pr_debug("EGPIO ack - write %x to base+%x\n", + ei->ack_write, ei->ack_register << ei->bus_shift); +} + +static void egpio_ack(unsigned int irq) +{ +} + +/* There does not appear to be a way to proactively mask interrupts + * on the egpio chip itself. So, we simply ignore interrupts that + * aren't desired. */ +static void egpio_mask(unsigned int irq) +{ + struct egpio_info *ei = get_irq_chip_data(irq); + ei->irqs_enabled &= ~(1 << (irq - ei->irq_start)); + pr_debug("EGPIO mask %d %04x\n", irq, ei->irqs_enabled); +} +static void egpio_unmask(unsigned int irq) +{ + struct egpio_info *ei = get_irq_chip_data(irq); + ei->irqs_enabled |= 1 << (irq - ei->irq_start); + pr_debug("EGPIO unmask %d %04x\n", irq, ei->irqs_enabled); +} + +static struct irq_chip egpio_muxed_chip = { + .name = "htc-egpio", + .ack = egpio_ack, + .mask = egpio_mask, + .unmask = egpio_unmask, +}; + +static void egpio_handler(unsigned int irq, struct irq_desc *desc) +{ + struct egpio_info *ei = get_irq_data(irq); + int irqpin; + + /* Read current pins. */ + unsigned long readval = egpio_readw(ei, ei->ack_register); + pr_debug("IRQ reg: %x\n", (unsigned int)readval); + /* Ack/unmask interrupts. */ + ack_irqs(ei); + /* Process all set pins. */ + readval &= ei->irqs_enabled; + for_each_bit(irqpin, &readval, ei->nirqs) { + /* Run irq handler */ + pr_debug("got IRQ %d\n", irqpin); + irq = ei->irq_start + irqpin; + desc = irq_to_desc(irq); + desc->handle_irq(irq, desc); + } +} + +int htc_egpio_get_wakeup_irq(struct device *dev) +{ + struct egpio_info *ei = dev_get_drvdata(dev); + + /* Read current pins. */ + u16 readval = egpio_readw(ei, ei->ack_register); + /* Ack/unmask interrupts. */ + ack_irqs(ei); + /* Return first set pin. */ + readval &= ei->irqs_enabled; + return ei->irq_start + ffs(readval) - 1; +} +EXPORT_SYMBOL(htc_egpio_get_wakeup_irq); + +static inline int egpio_pos(struct egpio_info *ei, int bit) +{ + return bit >> ei->reg_shift; +} + +static inline int egpio_bit(struct egpio_info *ei, int bit) +{ + return 1 << (bit & ((1 << ei->reg_shift)-1)); +} + +/* + * Input pins + */ + +static int egpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct egpio_chip *egpio; + struct egpio_info *ei; + unsigned bit; + int reg; + int value; + + pr_debug("egpio_get_value(%d)\n", chip->base + offset); + + egpio = container_of(chip, struct egpio_chip, chip); + ei = dev_get_drvdata(egpio->dev); + bit = egpio_bit(ei, offset); + reg = egpio->reg_start + egpio_pos(ei, offset); + + value = egpio_readw(ei, reg); + pr_debug("readw(%p + %x) = %x\n", + ei->base_addr, reg << ei->bus_shift, value); + return value & bit; +} + +static int egpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct egpio_chip *egpio; + + egpio = container_of(chip, struct egpio_chip, chip); + return test_bit(offset, &egpio->is_out) ? -EINVAL : 0; +} + + +/* + * Output pins + */ + +static void egpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + unsigned long flag; + struct egpio_chip *egpio; + struct egpio_info *ei; + unsigned bit; + int pos; + int reg; + int shift; + + pr_debug("egpio_set(%s, %d(%d), %d)\n", + chip->label, offset, offset+chip->base, value); + + egpio = container_of(chip, struct egpio_chip, chip); + ei = dev_get_drvdata(egpio->dev); + bit = egpio_bit(ei, offset); + pos = egpio_pos(ei, offset); + reg = egpio->reg_start + pos; + shift = pos << ei->reg_shift; + + pr_debug("egpio %s: reg %d = 0x%04x\n", value ? "set" : "clear", + reg, (egpio->cached_values >> shift) & ei->reg_mask); + + spin_lock_irqsave(&ei->lock, flag); + if (value) + egpio->cached_values |= (1 << offset); + else + egpio->cached_values &= ~(1 << offset); + egpio_writew((egpio->cached_values >> shift) & ei->reg_mask, ei, reg); + spin_unlock_irqrestore(&ei->lock, flag); +} + +static int egpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct egpio_chip *egpio; + + egpio = container_of(chip, struct egpio_chip, chip); + if (test_bit(offset, &egpio->is_out)) { + egpio_set(chip, offset, value); + return 0; + } else { + return -EINVAL; + } +} + +static void egpio_write_cache(struct egpio_info *ei) +{ + int i; + struct egpio_chip *egpio; + int shift; + + for (i = 0; i < ei->nchips; i++) { + egpio = &(ei->chip[i]); + if (!egpio->is_out) + continue; + + for (shift = 0; shift < egpio->chip.ngpio; + shift += (1<<ei->reg_shift)) { + + int reg = egpio->reg_start + egpio_pos(ei, shift); + + if (!((egpio->is_out >> shift) & ei->reg_mask)) + continue; + + pr_debug("EGPIO: setting %x to %x, was %x\n", reg, + (egpio->cached_values >> shift) & ei->reg_mask, + egpio_readw(ei, reg)); + + egpio_writew((egpio->cached_values >> shift) + & ei->reg_mask, ei, reg); + } + } +} + + +/* + * Setup + */ + +static int __init egpio_probe(struct platform_device *pdev) +{ + struct htc_egpio_platform_data *pdata = pdev->dev.platform_data; + struct resource *res; + struct egpio_info *ei; + struct gpio_chip *chip; + unsigned int irq, irq_end; + int i; + int ret; + + /* Initialize ei data structure. */ + ei = kzalloc(sizeof(*ei), GFP_KERNEL); + if (!ei) + return -ENOMEM; + + spin_lock_init(&ei->lock); + + /* Find chained irq */ + ret = -EINVAL; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res) + ei->chained_irq = res->start; + + /* Map egpio chip into virtual address space. */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + goto fail; + ei->base_addr = ioremap_nocache(res->start, resource_size(res)); + if (!ei->base_addr) + goto fail; + pr_debug("EGPIO phys=%08x virt=%p\n", (u32)res->start, ei->base_addr); + + if ((pdata->bus_width != 16) && (pdata->bus_width != 32)) + goto fail; + ei->bus_shift = fls(pdata->bus_width - 1) - 3; + pr_debug("bus_shift = %d\n", ei->bus_shift); + + if ((pdata->reg_width != 8) && (pdata->reg_width != 16)) + goto fail; + ei->reg_shift = fls(pdata->reg_width - 1); + pr_debug("reg_shift = %d\n", ei->reg_shift); + + ei->reg_mask = (1 << pdata->reg_width) - 1; + + platform_set_drvdata(pdev, ei); + + ei->nchips = pdata->num_chips; + ei->chip = kzalloc(sizeof(struct egpio_chip) * ei->nchips, GFP_KERNEL); + if (!ei->chip) { + ret = -ENOMEM; + goto fail; + } + for (i = 0; i < ei->nchips; i++) { + ei->chip[i].reg_start = pdata->chip[i].reg_start; + ei->chip[i].cached_values = pdata->chip[i].initial_values; + ei->chip[i].is_out = pdata->chip[i].direction; + ei->chip[i].dev = &(pdev->dev); + chip = &(ei->chip[i].chip); + chip->label = "htc-egpio"; + chip->dev = &pdev->dev; + chip->owner = THIS_MODULE; + chip->get = egpio_get; + chip->set = egpio_set; + chip->direction_input = egpio_direction_input; + chip->direction_output = egpio_direction_output; + chip->base = pdata->chip[i].gpio_base; + chip->ngpio = pdata->chip[i].num_gpios; + + gpiochip_add(chip); + } + + /* Set initial pin values */ + egpio_write_cache(ei); + + ei->irq_start = pdata->irq_base; + ei->nirqs = pdata->num_irqs; + ei->ack_register = pdata->ack_register; + + if (ei->chained_irq) { + /* Setup irq handlers */ + ei->ack_write = 0xFFFF; + if (pdata->invert_acks) + ei->ack_write = 0; + irq_end = ei->irq_start + ei->nirqs; + for (irq = ei->irq_start; irq < irq_end; irq++) { + set_irq_chip(irq, &egpio_muxed_chip); + set_irq_chip_data(irq, ei); + set_irq_handler(irq, handle_simple_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + } + set_irq_type(ei->chained_irq, IRQ_TYPE_EDGE_RISING); + set_irq_data(ei->chained_irq, ei); + set_irq_chained_handler(ei->chained_irq, egpio_handler); + ack_irqs(ei); + + device_init_wakeup(&pdev->dev, 1); + } + + return 0; + +fail: + printk(KERN_ERR "EGPIO failed to setup\n"); + kfree(ei); + return ret; +} + +static int __exit egpio_remove(struct platform_device *pdev) +{ + struct egpio_info *ei = platform_get_drvdata(pdev); + unsigned int irq, irq_end; + + if (ei->chained_irq) { + irq_end = ei->irq_start + ei->nirqs; + for (irq = ei->irq_start; irq < irq_end; irq++) { + set_irq_chip(irq, NULL); + set_irq_handler(irq, NULL); + set_irq_flags(irq, 0); + } + set_irq_chained_handler(ei->chained_irq, NULL); + device_init_wakeup(&pdev->dev, 0); + } + iounmap(ei->base_addr); + kfree(ei->chip); + kfree(ei); + + return 0; +} + +#ifdef CONFIG_PM +static int egpio_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct egpio_info *ei = platform_get_drvdata(pdev); + + if (ei->chained_irq && device_may_wakeup(&pdev->dev)) + enable_irq_wake(ei->chained_irq); + return 0; +} + +static int egpio_resume(struct platform_device *pdev) +{ + struct egpio_info *ei = platform_get_drvdata(pdev); + + if (ei->chained_irq && device_may_wakeup(&pdev->dev)) + disable_irq_wake(ei->chained_irq); + + /* Update registers from the cache, in case + the CPLD was powered off during suspend */ + egpio_write_cache(ei); + return 0; +} +#else +#define egpio_suspend NULL +#define egpio_resume NULL +#endif + + +static struct platform_driver egpio_driver = { + .driver = { + .name = "htc-egpio", + }, + .remove = __exit_p(egpio_remove), + .suspend = egpio_suspend, + .resume = egpio_resume, +}; + +static int __init egpio_init(void) +{ + return platform_driver_probe(&egpio_driver, egpio_probe); +} + +static void __exit egpio_exit(void) +{ + platform_driver_unregister(&egpio_driver); +} + +/* start early for dependencies */ +subsys_initcall(egpio_init); +module_exit(egpio_exit) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kevin O'Connor <kevin@koconnor.net>"); diff --git a/drivers/mfd/htc-pasic3.c b/drivers/mfd/htc-pasic3.c new file mode 100644 index 00000000000..cb73051e43d --- /dev/null +++ b/drivers/mfd/htc-pasic3.c @@ -0,0 +1,232 @@ +/* + * Core driver for HTC PASIC3 LED/DS1WM chip. + * + * Copyright (C) 2006 Philipp Zabel <philipp.zabel@gmail.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; version 2 of the License. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/mfd/core.h> +#include <linux/mfd/ds1wm.h> +#include <linux/mfd/htc-pasic3.h> + +struct pasic3_data { + void __iomem *mapping; + unsigned int bus_shift; +}; + +#define REG_ADDR 5 +#define REG_DATA 6 + +#define READ_MODE 0x80 + +/* + * write to a secondary register on the PASIC3 + */ +void pasic3_write_register(struct device *dev, u32 reg, u8 val) +{ + struct pasic3_data *asic = dev_get_drvdata(dev); + int bus_shift = asic->bus_shift; + void __iomem *addr = asic->mapping + (REG_ADDR << bus_shift); + void __iomem *data = asic->mapping + (REG_DATA << bus_shift); + + __raw_writeb(~READ_MODE & reg, addr); + __raw_writeb(val, data); +} +EXPORT_SYMBOL(pasic3_write_register); /* for leds-pasic3 */ + +/* + * read from a secondary register on the PASIC3 + */ +u8 pasic3_read_register(struct device *dev, u32 reg) +{ + struct pasic3_data *asic = dev_get_drvdata(dev); + int bus_shift = asic->bus_shift; + void __iomem *addr = asic->mapping + (REG_ADDR << bus_shift); + void __iomem *data = asic->mapping + (REG_DATA << bus_shift); + + __raw_writeb(READ_MODE | reg, addr); + return __raw_readb(data); +} +EXPORT_SYMBOL(pasic3_read_register); /* for leds-pasic3 */ + +/* + * LEDs + */ + +static struct mfd_cell led_cell __initdata = { + .name = "leds-pasic3", +}; + +/* + * DS1WM + */ + +static int ds1wm_enable(struct platform_device *pdev) +{ + struct device *dev = pdev->dev.parent; + int c; + + c = pasic3_read_register(dev, 0x28); + pasic3_write_register(dev, 0x28, c & 0x7f); + + dev_dbg(dev, "DS1WM OWM_EN low (active) %02x\n", c & 0x7f); + return 0; +} + +static int ds1wm_disable(struct platform_device *pdev) +{ + struct device *dev = pdev->dev.parent; + int c; + + c = pasic3_read_register(dev, 0x28); + pasic3_write_register(dev, 0x28, c | 0x80); + + dev_dbg(dev, "DS1WM OWM_EN high (inactive) %02x\n", c | 0x80); + return 0; +} + +static struct ds1wm_driver_data ds1wm_pdata = { + .active_high = 0, +}; + +static struct resource ds1wm_resources[] __initdata = { + [0] = { + .start = 0, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell ds1wm_cell __initdata = { + .name = "ds1wm", + .enable = ds1wm_enable, + .disable = ds1wm_disable, + .driver_data = &ds1wm_pdata, + .num_resources = 2, + .resources = ds1wm_resources, +}; + +static int __init pasic3_probe(struct platform_device *pdev) +{ + struct pasic3_platform_data *pdata = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct pasic3_data *asic; + struct resource *r; + int ret; + int irq = 0; + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (r) { + ds1wm_resources[1].flags = IORESOURCE_IRQ | (r->flags & + (IORESOURCE_IRQ_HIGHEDGE | IORESOURCE_IRQ_LOWEDGE)); + irq = r->start; + } + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (r) { + ds1wm_resources[1].flags = IORESOURCE_IRQ | (r->flags & + (IORESOURCE_IRQ_HIGHEDGE | IORESOURCE_IRQ_LOWEDGE)); + irq = r->start; + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) + return -ENXIO; + + if (!request_mem_region(r->start, resource_size(r), "pasic3")) + return -EBUSY; + + asic = kzalloc(sizeof(struct pasic3_data), GFP_KERNEL); + if (!asic) + return -ENOMEM; + + platform_set_drvdata(pdev, asic); + + asic->mapping = ioremap(r->start, resource_size(r)); + if (!asic->mapping) { + dev_err(dev, "couldn't ioremap PASIC3\n"); + kfree(asic); + return -ENOMEM; + } + + /* calculate bus shift from mem resource */ + asic->bus_shift = (resource_size(r) - 5) >> 3; + + if (pdata && pdata->clock_rate) { + ds1wm_pdata.clock_rate = pdata->clock_rate; + /* the first 5 PASIC3 registers control the DS1WM */ + ds1wm_resources[0].end = (5 << asic->bus_shift) - 1; + ds1wm_cell.platform_data = &ds1wm_cell; + ds1wm_cell.data_size = sizeof(ds1wm_cell); + ret = mfd_add_devices(&pdev->dev, pdev->id, + &ds1wm_cell, 1, r, irq); + if (ret < 0) + dev_warn(dev, "failed to register DS1WM\n"); + } + + if (pdata && pdata->led_pdata) { + led_cell.driver_data = pdata->led_pdata; + led_cell.platform_data = &led_cell; + led_cell.data_size = sizeof(ds1wm_cell); + ret = mfd_add_devices(&pdev->dev, pdev->id, &led_cell, 1, r, 0); + if (ret < 0) + dev_warn(dev, "failed to register LED device\n"); + } + + return 0; +} + +static int pasic3_remove(struct platform_device *pdev) +{ + struct pasic3_data *asic = platform_get_drvdata(pdev); + struct resource *r; + + mfd_remove_devices(&pdev->dev); + + iounmap(asic->mapping); + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, resource_size(r)); + kfree(asic); + return 0; +} + +MODULE_ALIAS("platform:pasic3"); + +static struct platform_driver pasic3_driver = { + .driver = { + .name = "pasic3", + }, + .remove = pasic3_remove, +}; + +static int __init pasic3_base_init(void) +{ + return platform_driver_probe(&pasic3_driver, pasic3_probe); +} + +static void __exit pasic3_base_exit(void) +{ + platform_driver_unregister(&pasic3_driver); +} + +module_init(pasic3_base_init); +module_exit(pasic3_base_exit); + +MODULE_AUTHOR("Philipp Zabel <philipp.zabel@gmail.com>"); +MODULE_DESCRIPTION("Core driver for HTC PASIC3"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/mc13783-core.c b/drivers/mfd/mc13783-core.c new file mode 100644 index 00000000000..735c8a4d164 --- /dev/null +++ b/drivers/mfd/mc13783-core.c @@ -0,0 +1,684 @@ +/* + * Copyright 2009 Pengutronix + * Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de> + * + * loosely based on an earlier driver that has + * Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de> + * + * 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 <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/mfd/core.h> +#include <linux/mfd/mc13783-private.h> + +#define MC13783_IRQSTAT0 0 +#define MC13783_IRQSTAT0_ADCDONEI (1 << 0) +#define MC13783_IRQSTAT0_ADCBISDONEI (1 << 1) +#define MC13783_IRQSTAT0_TSI (1 << 2) +#define MC13783_IRQSTAT0_WHIGHI (1 << 3) +#define MC13783_IRQSTAT0_WLOWI (1 << 4) +#define MC13783_IRQSTAT0_CHGDETI (1 << 6) +#define MC13783_IRQSTAT0_CHGOVI (1 << 7) +#define MC13783_IRQSTAT0_CHGREVI (1 << 8) +#define MC13783_IRQSTAT0_CHGSHORTI (1 << 9) +#define MC13783_IRQSTAT0_CCCVI (1 << 10) +#define MC13783_IRQSTAT0_CHGCURRI (1 << 11) +#define MC13783_IRQSTAT0_BPONI (1 << 12) +#define MC13783_IRQSTAT0_LOBATLI (1 << 13) +#define MC13783_IRQSTAT0_LOBATHI (1 << 14) +#define MC13783_IRQSTAT0_UDPI (1 << 15) +#define MC13783_IRQSTAT0_USBI (1 << 16) +#define MC13783_IRQSTAT0_IDI (1 << 19) +#define MC13783_IRQSTAT0_SE1I (1 << 21) +#define MC13783_IRQSTAT0_CKDETI (1 << 22) +#define MC13783_IRQSTAT0_UDMI (1 << 23) + +#define MC13783_IRQMASK0 1 +#define MC13783_IRQMASK0_ADCDONEM MC13783_IRQSTAT0_ADCDONEI +#define MC13783_IRQMASK0_ADCBISDONEM MC13783_IRQSTAT0_ADCBISDONEI +#define MC13783_IRQMASK0_TSM MC13783_IRQSTAT0_TSI +#define MC13783_IRQMASK0_WHIGHM MC13783_IRQSTAT0_WHIGHI +#define MC13783_IRQMASK0_WLOWM MC13783_IRQSTAT0_WLOWI +#define MC13783_IRQMASK0_CHGDETM MC13783_IRQSTAT0_CHGDETI +#define MC13783_IRQMASK0_CHGOVM MC13783_IRQSTAT0_CHGOVI +#define MC13783_IRQMASK0_CHGREVM MC13783_IRQSTAT0_CHGREVI +#define MC13783_IRQMASK0_CHGSHORTM MC13783_IRQSTAT0_CHGSHORTI +#define MC13783_IRQMASK0_CCCVM MC13783_IRQSTAT0_CCCVI +#define MC13783_IRQMASK0_CHGCURRM MC13783_IRQSTAT0_CHGCURRI +#define MC13783_IRQMASK0_BPONM MC13783_IRQSTAT0_BPONI +#define MC13783_IRQMASK0_LOBATLM MC13783_IRQSTAT0_LOBATLI +#define MC13783_IRQMASK0_LOBATHM MC13783_IRQSTAT0_LOBATHI +#define MC13783_IRQMASK0_UDPM MC13783_IRQSTAT0_UDPI +#define MC13783_IRQMASK0_USBM MC13783_IRQSTAT0_USBI +#define MC13783_IRQMASK0_IDM MC13783_IRQSTAT0_IDI +#define MC13783_IRQMASK0_SE1M MC13783_IRQSTAT0_SE1I +#define MC13783_IRQMASK0_CKDETM MC13783_IRQSTAT0_CKDETI +#define MC13783_IRQMASK0_UDMM MC13783_IRQSTAT0_UDMI + +#define MC13783_IRQSTAT1 3 +#define MC13783_IRQSTAT1_1HZI (1 << 0) +#define MC13783_IRQSTAT1_TODAI (1 << 1) +#define MC13783_IRQSTAT1_ONOFD1I (1 << 3) +#define MC13783_IRQSTAT1_ONOFD2I (1 << 4) +#define MC13783_IRQSTAT1_ONOFD3I (1 << 5) +#define MC13783_IRQSTAT1_SYSRSTI (1 << 6) +#define MC13783_IRQSTAT1_RTCRSTI (1 << 7) +#define MC13783_IRQSTAT1_PCI (1 << 8) +#define MC13783_IRQSTAT1_WARMI (1 << 9) +#define MC13783_IRQSTAT1_MEMHLDI (1 << 10) +#define MC13783_IRQSTAT1_PWRRDYI (1 << 11) +#define MC13783_IRQSTAT1_THWARNLI (1 << 12) +#define MC13783_IRQSTAT1_THWARNHI (1 << 13) +#define MC13783_IRQSTAT1_CLKI (1 << 14) +#define MC13783_IRQSTAT1_SEMAFI (1 << 15) +#define MC13783_IRQSTAT1_MC2BI (1 << 17) +#define MC13783_IRQSTAT1_HSDETI (1 << 18) +#define MC13783_IRQSTAT1_HSLI (1 << 19) +#define MC13783_IRQSTAT1_ALSPTHI (1 << 20) +#define MC13783_IRQSTAT1_AHSSHORTI (1 << 21) + +#define MC13783_IRQMASK1 4 +#define MC13783_IRQMASK1_1HZM MC13783_IRQSTAT1_1HZI +#define MC13783_IRQMASK1_TODAM MC13783_IRQSTAT1_TODAI +#define MC13783_IRQMASK1_ONOFD1M MC13783_IRQSTAT1_ONOFD1I +#define MC13783_IRQMASK1_ONOFD2M MC13783_IRQSTAT1_ONOFD2I +#define MC13783_IRQMASK1_ONOFD3M MC13783_IRQSTAT1_ONOFD3I +#define MC13783_IRQMASK1_SYSRSTM MC13783_IRQSTAT1_SYSRSTI +#define MC13783_IRQMASK1_RTCRSTM MC13783_IRQSTAT1_RTCRSTI +#define MC13783_IRQMASK1_PCM MC13783_IRQSTAT1_PCI +#define MC13783_IRQMASK1_WARMM MC13783_IRQSTAT1_WARMI +#define MC13783_IRQMASK1_MEMHLDM MC13783_IRQSTAT1_MEMHLDI +#define MC13783_IRQMASK1_PWRRDYM MC13783_IRQSTAT1_PWRRDYI +#define MC13783_IRQMASK1_THWARNLM MC13783_IRQSTAT1_THWARNLI +#define MC13783_IRQMASK1_THWARNHM MC13783_IRQSTAT1_THWARNHI +#define MC13783_IRQMASK1_CLKM MC13783_IRQSTAT1_CLKI +#define MC13783_IRQMASK1_SEMAFM MC13783_IRQSTAT1_SEMAFI +#define MC13783_IRQMASK1_MC2BM MC13783_IRQSTAT1_MC2BI +#define MC13783_IRQMASK1_HSDETM MC13783_IRQSTAT1_HSDETI +#define MC13783_IRQMASK1_HSLM MC13783_IRQSTAT1_HSLI +#define MC13783_IRQMASK1_ALSPTHM MC13783_IRQSTAT1_ALSPTHI +#define MC13783_IRQMASK1_AHSSHORTM MC13783_IRQSTAT1_AHSSHORTI + +#define MC13783_ADC1 44 +#define MC13783_ADC1_ADEN (1 << 0) +#define MC13783_ADC1_RAND (1 << 1) +#define MC13783_ADC1_ADSEL (1 << 3) +#define MC13783_ADC1_ASC (1 << 20) +#define MC13783_ADC1_ADTRIGIGN (1 << 21) + +#define MC13783_NUMREGS 0x3f + +void mc13783_lock(struct mc13783 *mc13783) +{ + if (!mutex_trylock(&mc13783->lock)) { + dev_dbg(&mc13783->spidev->dev, "wait for %s from %pf\n", + __func__, __builtin_return_address(0)); + + mutex_lock(&mc13783->lock); + } + dev_dbg(&mc13783->spidev->dev, "%s from %pf\n", + __func__, __builtin_return_address(0)); +} +EXPORT_SYMBOL(mc13783_lock); + +void mc13783_unlock(struct mc13783 *mc13783) +{ + dev_dbg(&mc13783->spidev->dev, "%s from %pf\n", + __func__, __builtin_return_address(0)); + mutex_unlock(&mc13783->lock); +} +EXPORT_SYMBOL(mc13783_unlock); + +#define MC13783_REGOFFSET_SHIFT 25 +int mc13783_reg_read(struct mc13783 *mc13783, unsigned int offset, u32 *val) +{ + struct spi_transfer t; + struct spi_message m; + int ret; + + BUG_ON(!mutex_is_locked(&mc13783->lock)); + + if (offset > MC13783_NUMREGS) + return -EINVAL; + + *val = offset << MC13783_REGOFFSET_SHIFT; + + memset(&t, 0, sizeof(t)); + + t.tx_buf = val; + t.rx_buf = val; + t.len = sizeof(u32); + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + ret = spi_sync(mc13783->spidev, &m); + + /* error in message.status implies error return from spi_sync */ + BUG_ON(!ret && m.status); + + if (ret) + return ret; + + *val &= 0xffffff; + + dev_vdbg(&mc13783->spidev->dev, "[0x%02x] -> 0x%06x\n", offset, *val); + + return 0; +} +EXPORT_SYMBOL(mc13783_reg_read); + +int mc13783_reg_write(struct mc13783 *mc13783, unsigned int offset, u32 val) +{ + u32 buf; + struct spi_transfer t; + struct spi_message m; + int ret; + + BUG_ON(!mutex_is_locked(&mc13783->lock)); + + dev_vdbg(&mc13783->spidev->dev, "[0x%02x] <- 0x%06x\n", offset, val); + + if (offset > MC13783_NUMREGS || val > 0xffffff) + return -EINVAL; + + buf = 1 << 31 | offset << MC13783_REGOFFSET_SHIFT | val; + + memset(&t, 0, sizeof(t)); + + t.tx_buf = &buf; + t.rx_buf = &buf; + t.len = sizeof(u32); + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + ret = spi_sync(mc13783->spidev, &m); + + BUG_ON(!ret && m.status); + + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL(mc13783_reg_write); + +int mc13783_reg_rmw(struct mc13783 *mc13783, unsigned int offset, + u32 mask, u32 val) +{ + int ret; + u32 valread; + + BUG_ON(val & ~mask); + + ret = mc13783_reg_read(mc13783, offset, &valread); + if (ret) + return ret; + + valread = (valread & ~mask) | val; + + return mc13783_reg_write(mc13783, offset, valread); +} +EXPORT_SYMBOL(mc13783_reg_rmw); + +int mc13783_mask(struct mc13783 *mc13783, int irq) +{ + int ret; + unsigned int offmask = irq < 24 ? MC13783_IRQMASK0 : MC13783_IRQMASK1; + u32 irqbit = 1 << (irq < 24 ? irq : irq - 24); + u32 mask; + + if (irq < 0 || irq >= MC13783_NUM_IRQ) + return -EINVAL; + + ret = mc13783_reg_read(mc13783, offmask, &mask); + if (ret) + return ret; + + if (mask & irqbit) + /* already masked */ + return 0; + + return mc13783_reg_write(mc13783, offmask, mask | irqbit); +} +EXPORT_SYMBOL(mc13783_mask); + +int mc13783_unmask(struct mc13783 *mc13783, int irq) +{ + int ret; + unsigned int offmask = irq < 24 ? MC13783_IRQMASK0 : MC13783_IRQMASK1; + u32 irqbit = 1 << (irq < 24 ? irq : irq - 24); + u32 mask; + + if (irq < 0 || irq >= MC13783_NUM_IRQ) + return -EINVAL; + + ret = mc13783_reg_read(mc13783, offmask, &mask); + if (ret) + return ret; + + if (!(mask & irqbit)) + /* already unmasked */ + return 0; + + return mc13783_reg_write(mc13783, offmask, mask & ~irqbit); +} +EXPORT_SYMBOL(mc13783_unmask); + +int mc13783_irq_request_nounmask(struct mc13783 *mc13783, int irq, + irq_handler_t handler, const char *name, void *dev) +{ + BUG_ON(!mutex_is_locked(&mc13783->lock)); + BUG_ON(!handler); + + if (irq < 0 || irq >= MC13783_NUM_IRQ) + return -EINVAL; + + if (mc13783->irqhandler[irq]) + return -EBUSY; + + mc13783->irqhandler[irq] = handler; + mc13783->irqdata[irq] = dev; + + return 0; +} +EXPORT_SYMBOL(mc13783_irq_request_nounmask); + +int mc13783_irq_request(struct mc13783 *mc13783, int irq, + irq_handler_t handler, const char *name, void *dev) +{ + int ret; + + ret = mc13783_irq_request_nounmask(mc13783, irq, handler, name, dev); + if (ret) + return ret; + + ret = mc13783_unmask(mc13783, irq); + if (ret) { + mc13783->irqhandler[irq] = NULL; + mc13783->irqdata[irq] = NULL; + return ret; + } + + return 0; +} +EXPORT_SYMBOL(mc13783_irq_request); + +int mc13783_irq_free(struct mc13783 *mc13783, int irq, void *dev) +{ + int ret; + BUG_ON(!mutex_is_locked(&mc13783->lock)); + + if (irq < 0 || irq >= MC13783_NUM_IRQ || !mc13783->irqhandler[irq] || + mc13783->irqdata[irq] != dev) + return -EINVAL; + + ret = mc13783_mask(mc13783, irq); + if (ret) + return ret; + + mc13783->irqhandler[irq] = NULL; + mc13783->irqdata[irq] = NULL; + + return 0; +} +EXPORT_SYMBOL(mc13783_irq_free); + +static inline irqreturn_t mc13783_irqhandler(struct mc13783 *mc13783, int irq) +{ + return mc13783->irqhandler[irq](irq, mc13783->irqdata[irq]); +} + +int mc13783_ackirq(struct mc13783 *mc13783, int irq) +{ + unsigned int offstat = irq < 24 ? MC13783_IRQSTAT0 : MC13783_IRQSTAT1; + unsigned int val = 1 << (irq < 24 ? irq : irq - 24); + + BUG_ON(irq < 0 || irq >= MC13783_NUM_IRQ); + + return mc13783_reg_write(mc13783, offstat, val); +} +EXPORT_SYMBOL(mc13783_ackirq); + +/* + * returns: number of handled irqs or negative error + * locking: holds mc13783->lock + */ +static int mc13783_irq_handle(struct mc13783 *mc13783, + unsigned int offstat, unsigned int offmask, int baseirq) +{ + u32 stat, mask; + int ret = mc13783_reg_read(mc13783, offstat, &stat); + int num_handled = 0; + + if (ret) + return ret; + + ret = mc13783_reg_read(mc13783, offmask, &mask); + if (ret) + return ret; + + while (stat & ~mask) { + int irq = __ffs(stat & ~mask); + + stat &= ~(1 << irq); + + if (likely(mc13783->irqhandler[baseirq + irq])) { + irqreturn_t handled; + + handled = mc13783_irqhandler(mc13783, baseirq + irq); + if (handled == IRQ_HANDLED) + num_handled++; + } else { + dev_err(&mc13783->spidev->dev, + "BUG: irq %u but no handler\n", + baseirq + irq); + + mask |= 1 << irq; + + ret = mc13783_reg_write(mc13783, offmask, mask); + } + } + + return num_handled; +} + +static irqreturn_t mc13783_irq_thread(int irq, void *data) +{ + struct mc13783 *mc13783 = data; + irqreturn_t ret; + int handled = 0; + + mc13783_lock(mc13783); + + ret = mc13783_irq_handle(mc13783, MC13783_IRQSTAT0, + MC13783_IRQMASK0, MC13783_IRQ_ADCDONE); + if (ret > 0) + handled = 1; + + ret = mc13783_irq_handle(mc13783, MC13783_IRQSTAT1, + MC13783_IRQMASK1, MC13783_IRQ_1HZ); + if (ret > 0) + handled = 1; + + mc13783_unlock(mc13783); + + return IRQ_RETVAL(handled); +} + +#define MC13783_ADC1_CHAN0_SHIFT 5 +#define MC13783_ADC1_CHAN1_SHIFT 8 + +struct mc13783_adcdone_data { + struct mc13783 *mc13783; + struct completion done; +}; + +static irqreturn_t mc13783_handler_adcdone(int irq, void *data) +{ + struct mc13783_adcdone_data *adcdone_data = data; + + mc13783_ackirq(adcdone_data->mc13783, irq); + + complete_all(&adcdone_data->done); + + return IRQ_HANDLED; +} + +#define MC13783_ADC_WORKING (1 << 16) + +int mc13783_adc_do_conversion(struct mc13783 *mc13783, unsigned int mode, + unsigned int channel, unsigned int *sample) +{ + u32 adc0, adc1, old_adc0; + int i, ret; + struct mc13783_adcdone_data adcdone_data = { + .mc13783 = mc13783, + }; + init_completion(&adcdone_data.done); + + dev_dbg(&mc13783->spidev->dev, "%s\n", __func__); + + mc13783_lock(mc13783); + + if (mc13783->flags & MC13783_ADC_WORKING) { + ret = -EBUSY; + goto out; + } + + mc13783->flags |= MC13783_ADC_WORKING; + + mc13783_reg_read(mc13783, MC13783_ADC0, &old_adc0); + + adc0 = MC13783_ADC0_ADINC1 | MC13783_ADC0_ADINC2; + adc1 = MC13783_ADC1_ADEN | MC13783_ADC1_ADTRIGIGN | MC13783_ADC1_ASC; + + if (channel > 7) + adc1 |= MC13783_ADC1_ADSEL; + + switch (mode) { + case MC13783_ADC_MODE_TS: + adc0 |= MC13783_ADC0_ADREFEN | MC13783_ADC0_TSMOD0 | + MC13783_ADC0_TSMOD1; + adc1 |= 4 << MC13783_ADC1_CHAN1_SHIFT; + break; + + case MC13783_ADC_MODE_SINGLE_CHAN: + adc0 |= old_adc0 & MC13783_ADC0_TSMOD_MASK; + adc1 |= (channel & 0x7) << MC13783_ADC1_CHAN0_SHIFT; + adc1 |= MC13783_ADC1_RAND; + break; + + case MC13783_ADC_MODE_MULT_CHAN: + adc0 |= old_adc0 & MC13783_ADC0_TSMOD_MASK; + adc1 |= 4 << MC13783_ADC1_CHAN1_SHIFT; + break; + + default: + mc13783_unlock(mc13783); + return -EINVAL; + } + + dev_dbg(&mc13783->spidev->dev, "%s: request irq\n", __func__); + mc13783_irq_request(mc13783, MC13783_IRQ_ADCDONE, + mc13783_handler_adcdone, __func__, &adcdone_data); + mc13783_ackirq(mc13783, MC13783_IRQ_ADCDONE); + + mc13783_reg_write(mc13783, MC13783_REG_ADC_0, adc0); + mc13783_reg_write(mc13783, MC13783_REG_ADC_1, adc1); + + mc13783_unlock(mc13783); + + ret = wait_for_completion_interruptible_timeout(&adcdone_data.done, HZ); + + if (!ret) + ret = -ETIMEDOUT; + + mc13783_lock(mc13783); + + mc13783_irq_free(mc13783, MC13783_IRQ_ADCDONE, &adcdone_data); + + if (ret > 0) + for (i = 0; i < 4; ++i) { + ret = mc13783_reg_read(mc13783, + MC13783_REG_ADC_2, &sample[i]); + if (ret) + break; + } + + if (mode == MC13783_ADC_MODE_TS) + /* restore TSMOD */ + mc13783_reg_write(mc13783, MC13783_REG_ADC_0, old_adc0); + + mc13783->flags &= ~MC13783_ADC_WORKING; +out: + mc13783_unlock(mc13783); + + return ret; +} +EXPORT_SYMBOL_GPL(mc13783_adc_do_conversion); + +static int mc13783_add_subdevice_pdata(struct mc13783 *mc13783, + const char *name, void *pdata, size_t pdata_size) +{ + struct mfd_cell cell = { + .name = name, + .platform_data = pdata, + .data_size = pdata_size, + }; + + return mfd_add_devices(&mc13783->spidev->dev, -1, &cell, 1, NULL, 0); +} + +static int mc13783_add_subdevice(struct mc13783 *mc13783, const char *name) +{ + return mc13783_add_subdevice_pdata(mc13783, name, NULL, 0); +} + +static int mc13783_check_revision(struct mc13783 *mc13783) +{ + u32 rev_id, rev1, rev2, finid, icid; + + mc13783_reg_read(mc13783, MC13783_REG_REVISION, &rev_id); + + rev1 = (rev_id & 0x018) >> 3; + rev2 = (rev_id & 0x007); + icid = (rev_id & 0x01C0) >> 6; + finid = (rev_id & 0x01E00) >> 9; + + /* Ver 0.2 is actually 3.2a. Report as 3.2 */ + if ((rev1 == 0) && (rev2 == 2)) + rev1 = 3; + + if (rev1 == 0 || icid != 2) { + dev_err(&mc13783->spidev->dev, "No MC13783 detected.\n"); + return -ENODEV; + } + + dev_info(&mc13783->spidev->dev, + "MC13783 Rev %d.%d FinVer %x detected\n", + rev1, rev2, finid); + + return 0; +} + +static int mc13783_probe(struct spi_device *spi) +{ + struct mc13783 *mc13783; + struct mc13783_platform_data *pdata = dev_get_platdata(&spi->dev); + int ret; + + mc13783 = kzalloc(sizeof(*mc13783), GFP_KERNEL); + if (!mc13783) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, mc13783); + spi->mode = SPI_MODE_0 | SPI_CS_HIGH; + spi->bits_per_word = 32; + spi_setup(spi); + + mc13783->spidev = spi; + + mutex_init(&mc13783->lock); + mc13783_lock(mc13783); + + ret = mc13783_check_revision(mc13783); + if (ret) + goto err_revision; + + /* mask all irqs */ + ret = mc13783_reg_write(mc13783, MC13783_IRQMASK0, 0x00ffffff); + if (ret) + goto err_mask; + + ret = mc13783_reg_write(mc13783, MC13783_IRQMASK1, 0x00ffffff); + if (ret) + goto err_mask; + + ret = request_threaded_irq(spi->irq, NULL, mc13783_irq_thread, + IRQF_ONESHOT | IRQF_TRIGGER_HIGH, "mc13783", mc13783); + + if (ret) { +err_mask: +err_revision: + mutex_unlock(&mc13783->lock); + dev_set_drvdata(&spi->dev, NULL); + kfree(mc13783); + return ret; + } + + /* This should go away (BEGIN) */ + if (pdata) { + mc13783->flags = pdata->flags; + mc13783->regulators = pdata->regulators; + mc13783->num_regulators = pdata->num_regulators; + } + /* This should go away (END) */ + + mc13783_unlock(mc13783); + + if (pdata->flags & MC13783_USE_ADC) + mc13783_add_subdevice(mc13783, "mc13783-adc"); + + if (pdata->flags & MC13783_USE_CODEC) + mc13783_add_subdevice(mc13783, "mc13783-codec"); + + if (pdata->flags & MC13783_USE_REGULATOR) { + struct mc13783_regulator_platform_data regulator_pdata = { + .num_regulators = pdata->num_regulators, + .regulators = pdata->regulators, + }; + + mc13783_add_subdevice_pdata(mc13783, "mc13783-regulator", + ®ulator_pdata, sizeof(regulator_pdata)); + } + + if (pdata->flags & MC13783_USE_RTC) + mc13783_add_subdevice(mc13783, "mc13783-rtc"); + + if (pdata->flags & MC13783_USE_TOUCHSCREEN) + mc13783_add_subdevice(mc13783, "mc13783-ts"); + + return 0; +} + +static int __devexit mc13783_remove(struct spi_device *spi) +{ + struct mc13783 *mc13783 = dev_get_drvdata(&spi->dev); + + free_irq(mc13783->spidev->irq, mc13783); + + mfd_remove_devices(&spi->dev); + + return 0; +} + +static struct spi_driver mc13783_driver = { + .driver = { + .name = "mc13783", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = mc13783_probe, + .remove = __devexit_p(mc13783_remove), +}; + +static int __init mc13783_init(void) +{ + return spi_register_driver(&mc13783_driver); +} +subsys_initcall(mc13783_init); + +static void __exit mc13783_exit(void) +{ + spi_unregister_driver(&mc13783_driver); +} +module_exit(mc13783_exit); + +MODULE_DESCRIPTION("Core driver for Freescale MC13783 PMIC"); +MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/mcp-core.c b/drivers/mfd/mcp-core.c new file mode 100644 index 00000000000..84815f9ef63 --- /dev/null +++ b/drivers/mfd/mcp-core.c @@ -0,0 +1,256 @@ +/* + * linux/drivers/mfd/mcp-core.c + * + * Copyright (C) 2001 Russell King + * + * 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. + * + * Generic MCP (Multimedia Communications Port) layer. All MCP locking + * is solely held within this file. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/smp.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/mfd/mcp.h> + +#include <mach/dma.h> +#include <asm/system.h> + + +#define to_mcp(d) container_of(d, struct mcp, attached_device) +#define to_mcp_driver(d) container_of(d, struct mcp_driver, drv) + +static int mcp_bus_match(struct device *dev, struct device_driver *drv) +{ + return 1; +} + +static int mcp_bus_probe(struct device *dev) +{ + struct mcp *mcp = to_mcp(dev); + struct mcp_driver *drv = to_mcp_driver(dev->driver); + + return drv->probe(mcp); +} + +static int mcp_bus_remove(struct device *dev) +{ + struct mcp *mcp = to_mcp(dev); + struct mcp_driver *drv = to_mcp_driver(dev->driver); + + drv->remove(mcp); + return 0; +} + +static int mcp_bus_suspend(struct device *dev, pm_message_t state) +{ + struct mcp *mcp = to_mcp(dev); + int ret = 0; + + if (dev->driver) { + struct mcp_driver *drv = to_mcp_driver(dev->driver); + + ret = drv->suspend(mcp, state); + } + return ret; +} + +static int mcp_bus_resume(struct device *dev) +{ + struct mcp *mcp = to_mcp(dev); + int ret = 0; + + if (dev->driver) { + struct mcp_driver *drv = to_mcp_driver(dev->driver); + + ret = drv->resume(mcp); + } + return ret; +} + +static struct bus_type mcp_bus_type = { + .name = "mcp", + .match = mcp_bus_match, + .probe = mcp_bus_probe, + .remove = mcp_bus_remove, + .suspend = mcp_bus_suspend, + .resume = mcp_bus_resume, +}; + +/** + * mcp_set_telecom_divisor - set the telecom divisor + * @mcp: MCP interface structure + * @div: SIB clock divisor + * + * Set the telecom divisor on the MCP interface. The resulting + * sample rate is SIBCLOCK/div. + */ +void mcp_set_telecom_divisor(struct mcp *mcp, unsigned int div) +{ + spin_lock_irq(&mcp->lock); + mcp->ops->set_telecom_divisor(mcp, div); + spin_unlock_irq(&mcp->lock); +} +EXPORT_SYMBOL(mcp_set_telecom_divisor); + +/** + * mcp_set_audio_divisor - set the audio divisor + * @mcp: MCP interface structure + * @div: SIB clock divisor + * + * Set the audio divisor on the MCP interface. + */ +void mcp_set_audio_divisor(struct mcp *mcp, unsigned int div) +{ + spin_lock_irq(&mcp->lock); + mcp->ops->set_audio_divisor(mcp, div); + spin_unlock_irq(&mcp->lock); +} +EXPORT_SYMBOL(mcp_set_audio_divisor); + +/** + * mcp_reg_write - write a device register + * @mcp: MCP interface structure + * @reg: 4-bit register index + * @val: 16-bit data value + * + * Write a device register. The MCP interface must be enabled + * to prevent this function hanging. + */ +void mcp_reg_write(struct mcp *mcp, unsigned int reg, unsigned int val) +{ + unsigned long flags; + + spin_lock_irqsave(&mcp->lock, flags); + mcp->ops->reg_write(mcp, reg, val); + spin_unlock_irqrestore(&mcp->lock, flags); +} +EXPORT_SYMBOL(mcp_reg_write); + +/** + * mcp_reg_read - read a device register + * @mcp: MCP interface structure + * @reg: 4-bit register index + * + * Read a device register and return its value. The MCP interface + * must be enabled to prevent this function hanging. + */ +unsigned int mcp_reg_read(struct mcp *mcp, unsigned int reg) +{ + unsigned long flags; + unsigned int val; + + spin_lock_irqsave(&mcp->lock, flags); + val = mcp->ops->reg_read(mcp, reg); + spin_unlock_irqrestore(&mcp->lock, flags); + + return val; +} +EXPORT_SYMBOL(mcp_reg_read); + +/** + * mcp_enable - enable the MCP interface + * @mcp: MCP interface to enable + * + * Enable the MCP interface. Each call to mcp_enable will need + * a corresponding call to mcp_disable to disable the interface. + */ +void mcp_enable(struct mcp *mcp) +{ + spin_lock_irq(&mcp->lock); + if (mcp->use_count++ == 0) + mcp->ops->enable(mcp); + spin_unlock_irq(&mcp->lock); +} +EXPORT_SYMBOL(mcp_enable); + +/** + * mcp_disable - disable the MCP interface + * @mcp: MCP interface to disable + * + * Disable the MCP interface. The MCP interface will only be + * disabled once the number of calls to mcp_enable matches the + * number of calls to mcp_disable. + */ +void mcp_disable(struct mcp *mcp) +{ + unsigned long flags; + + spin_lock_irqsave(&mcp->lock, flags); + if (--mcp->use_count == 0) + mcp->ops->disable(mcp); + spin_unlock_irqrestore(&mcp->lock, flags); +} +EXPORT_SYMBOL(mcp_disable); + +static void mcp_release(struct device *dev) +{ + struct mcp *mcp = container_of(dev, struct mcp, attached_device); + + kfree(mcp); +} + +struct mcp *mcp_host_alloc(struct device *parent, size_t size) +{ + struct mcp *mcp; + + mcp = kzalloc(sizeof(struct mcp) + size, GFP_KERNEL); + if (mcp) { + spin_lock_init(&mcp->lock); + mcp->attached_device.parent = parent; + mcp->attached_device.bus = &mcp_bus_type; + mcp->attached_device.dma_mask = parent->dma_mask; + mcp->attached_device.release = mcp_release; + } + return mcp; +} +EXPORT_SYMBOL(mcp_host_alloc); + +int mcp_host_register(struct mcp *mcp) +{ + dev_set_name(&mcp->attached_device, "mcp0"); + return device_register(&mcp->attached_device); +} +EXPORT_SYMBOL(mcp_host_register); + +void mcp_host_unregister(struct mcp *mcp) +{ + device_unregister(&mcp->attached_device); +} +EXPORT_SYMBOL(mcp_host_unregister); + +int mcp_driver_register(struct mcp_driver *mcpdrv) +{ + mcpdrv->drv.bus = &mcp_bus_type; + return driver_register(&mcpdrv->drv); +} +EXPORT_SYMBOL(mcp_driver_register); + +void mcp_driver_unregister(struct mcp_driver *mcpdrv) +{ + driver_unregister(&mcpdrv->drv); +} +EXPORT_SYMBOL(mcp_driver_unregister); + +static int __init mcp_init(void) +{ + return bus_register(&mcp_bus_type); +} + +static void __exit mcp_exit(void) +{ + bus_unregister(&mcp_bus_type); +} + +module_init(mcp_init); +module_exit(mcp_exit); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("Core multimedia communications port driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/mcp-sa11x0.c b/drivers/mfd/mcp-sa11x0.c new file mode 100644 index 00000000000..25842723272 --- /dev/null +++ b/drivers/mfd/mcp-sa11x0.c @@ -0,0 +1,276 @@ +/* + * linux/drivers/mfd/mcp-sa11x0.c + * + * Copyright (C) 2001-2005 Russell King + * + * 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. + * + * SA11x0 MCP (Multimedia Communications Port) driver. + * + * MCP read/write timeouts from Jordi Colomer, rehacked by rmk. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/mfd/mcp.h> + +#include <mach/dma.h> +#include <mach/hardware.h> +#include <asm/mach-types.h> +#include <asm/system.h> +#include <mach/mcp.h> + +#include <mach/assabet.h> + + +struct mcp_sa11x0 { + u32 mccr0; + u32 mccr1; +}; + +#define priv(mcp) ((struct mcp_sa11x0 *)mcp_priv(mcp)) + +static void +mcp_sa11x0_set_telecom_divisor(struct mcp *mcp, unsigned int divisor) +{ + unsigned int mccr0; + + divisor /= 32; + + mccr0 = Ser4MCCR0 & ~0x00007f00; + mccr0 |= divisor << 8; + Ser4MCCR0 = mccr0; +} + +static void +mcp_sa11x0_set_audio_divisor(struct mcp *mcp, unsigned int divisor) +{ + unsigned int mccr0; + + divisor /= 32; + + mccr0 = Ser4MCCR0 & ~0x0000007f; + mccr0 |= divisor; + Ser4MCCR0 = mccr0; +} + +/* + * Write data to the device. The bit should be set after 3 subframe + * times (each frame is 64 clocks). We wait a maximum of 6 subframes. + * We really should try doing something more productive while we + * wait. + */ +static void +mcp_sa11x0_write(struct mcp *mcp, unsigned int reg, unsigned int val) +{ + int ret = -ETIME; + int i; + + Ser4MCDR2 = reg << 17 | MCDR2_Wr | (val & 0xffff); + + for (i = 0; i < 2; i++) { + udelay(mcp->rw_timeout); + if (Ser4MCSR & MCSR_CWC) { + ret = 0; + break; + } + } + + if (ret < 0) + printk(KERN_WARNING "mcp: write timed out\n"); +} + +/* + * Read data from the device. The bit should be set after 3 subframe + * times (each frame is 64 clocks). We wait a maximum of 6 subframes. + * We really should try doing something more productive while we + * wait. + */ +static unsigned int +mcp_sa11x0_read(struct mcp *mcp, unsigned int reg) +{ + int ret = -ETIME; + int i; + + Ser4MCDR2 = reg << 17 | MCDR2_Rd; + + for (i = 0; i < 2; i++) { + udelay(mcp->rw_timeout); + if (Ser4MCSR & MCSR_CRC) { + ret = Ser4MCDR2 & 0xffff; + break; + } + } + + if (ret < 0) + printk(KERN_WARNING "mcp: read timed out\n"); + + return ret; +} + +static void mcp_sa11x0_enable(struct mcp *mcp) +{ + Ser4MCSR = -1; + Ser4MCCR0 |= MCCR0_MCE; +} + +static void mcp_sa11x0_disable(struct mcp *mcp) +{ + Ser4MCCR0 &= ~MCCR0_MCE; +} + +/* + * Our methods. + */ +static struct mcp_ops mcp_sa11x0 = { + .set_telecom_divisor = mcp_sa11x0_set_telecom_divisor, + .set_audio_divisor = mcp_sa11x0_set_audio_divisor, + .reg_write = mcp_sa11x0_write, + .reg_read = mcp_sa11x0_read, + .enable = mcp_sa11x0_enable, + .disable = mcp_sa11x0_disable, +}; + +static int mcp_sa11x0_probe(struct platform_device *pdev) +{ + struct mcp_plat_data *data = pdev->dev.platform_data; + struct mcp *mcp; + int ret; + + if (!data) + return -ENODEV; + + if (!request_mem_region(0x80060000, 0x60, "sa11x0-mcp")) + return -EBUSY; + + mcp = mcp_host_alloc(&pdev->dev, sizeof(struct mcp_sa11x0)); + if (!mcp) { + ret = -ENOMEM; + goto release; + } + + mcp->owner = THIS_MODULE; + mcp->ops = &mcp_sa11x0; + mcp->sclk_rate = data->sclk_rate; + mcp->dma_audio_rd = DMA_Ser4MCP0Rd; + mcp->dma_audio_wr = DMA_Ser4MCP0Wr; + mcp->dma_telco_rd = DMA_Ser4MCP1Rd; + mcp->dma_telco_wr = DMA_Ser4MCP1Wr; + mcp->gpio_base = data->gpio_base; + + platform_set_drvdata(pdev, mcp); + + if (machine_is_assabet()) { + ASSABET_BCR_set(ASSABET_BCR_CODEC_RST); + } + + /* + * Setup the PPC unit correctly. + */ + PPDR &= ~PPC_RXD4; + PPDR |= PPC_TXD4 | PPC_SCLK | PPC_SFRM; + PSDR |= PPC_RXD4; + PSDR &= ~(PPC_TXD4 | PPC_SCLK | PPC_SFRM); + PPSR &= ~(PPC_TXD4 | PPC_SCLK | PPC_SFRM); + + /* + * Initialise device. Note that we initially + * set the sampling rate to minimum. + */ + Ser4MCSR = -1; + Ser4MCCR1 = data->mccr1; + Ser4MCCR0 = data->mccr0 | 0x7f7f; + + /* + * Calculate the read/write timeout (us) from the bit clock + * rate. This is the period for 3 64-bit frames. Always + * round this time up. + */ + mcp->rw_timeout = (64 * 3 * 1000000 + mcp->sclk_rate - 1) / + mcp->sclk_rate; + + ret = mcp_host_register(mcp); + if (ret == 0) + goto out; + + release: + release_mem_region(0x80060000, 0x60); + platform_set_drvdata(pdev, NULL); + + out: + return ret; +} + +static int mcp_sa11x0_remove(struct platform_device *dev) +{ + struct mcp *mcp = platform_get_drvdata(dev); + + platform_set_drvdata(dev, NULL); + mcp_host_unregister(mcp); + release_mem_region(0x80060000, 0x60); + + return 0; +} + +static int mcp_sa11x0_suspend(struct platform_device *dev, pm_message_t state) +{ + struct mcp *mcp = platform_get_drvdata(dev); + + priv(mcp)->mccr0 = Ser4MCCR0; + priv(mcp)->mccr1 = Ser4MCCR1; + Ser4MCCR0 &= ~MCCR0_MCE; + + return 0; +} + +static int mcp_sa11x0_resume(struct platform_device *dev) +{ + struct mcp *mcp = platform_get_drvdata(dev); + + Ser4MCCR1 = priv(mcp)->mccr1; + Ser4MCCR0 = priv(mcp)->mccr0; + + return 0; +} + +/* + * The driver for the SA11x0 MCP port. + */ +MODULE_ALIAS("platform:sa11x0-mcp"); + +static struct platform_driver mcp_sa11x0_driver = { + .probe = mcp_sa11x0_probe, + .remove = mcp_sa11x0_remove, + .suspend = mcp_sa11x0_suspend, + .resume = mcp_sa11x0_resume, + .driver = { + .name = "sa11x0-mcp", + }, +}; + +/* + * This needs re-working + */ +static int __init mcp_sa11x0_init(void) +{ + return platform_driver_register(&mcp_sa11x0_driver); +} + +static void __exit mcp_sa11x0_exit(void) +{ + platform_driver_unregister(&mcp_sa11x0_driver); +} + +module_init(mcp_sa11x0_init); +module_exit(mcp_sa11x0_exit); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("SA11x0 multimedia communications port driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/menelaus.c b/drivers/mfd/menelaus.c new file mode 100644 index 00000000000..970afa10326 --- /dev/null +++ b/drivers/mfd/menelaus.c @@ -0,0 +1,1285 @@ +/* + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Some parts based tps65010.c: + * Copyright (C) 2004 Texas Instruments and + * Copyright (C) 2004-2005 David Brownell + * + * Some parts based on tlv320aic24.c: + * Copyright (C) by Kai Svahn <kai.svahn@nokia.com> + * + * Changes for interrupt handling and clean-up by + * Tony Lindgren <tony@atomide.com> and Imre Deak <imre.deak@nokia.com> + * Cleanup and generalized support for voltage setting by + * Juha Yrjola + * Added support for controlling VCORE and regulator sleep states, + * Amit Kucheria <amit.kucheria@nokia.com> + * Copyright (C) 2005, 2006 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/rtc.h> +#include <linux/bcd.h> + +#include <asm/mach/irq.h> + +#include <mach/gpio.h> +#include <plat/menelaus.h> + +#define DRIVER_NAME "menelaus" + +#define MENELAUS_I2C_ADDRESS 0x72 + +#define MENELAUS_REV 0x01 +#define MENELAUS_VCORE_CTRL1 0x02 +#define MENELAUS_VCORE_CTRL2 0x03 +#define MENELAUS_VCORE_CTRL3 0x04 +#define MENELAUS_VCORE_CTRL4 0x05 +#define MENELAUS_VCORE_CTRL5 0x06 +#define MENELAUS_DCDC_CTRL1 0x07 +#define MENELAUS_DCDC_CTRL2 0x08 +#define MENELAUS_DCDC_CTRL3 0x09 +#define MENELAUS_LDO_CTRL1 0x0A +#define MENELAUS_LDO_CTRL2 0x0B +#define MENELAUS_LDO_CTRL3 0x0C +#define MENELAUS_LDO_CTRL4 0x0D +#define MENELAUS_LDO_CTRL5 0x0E +#define MENELAUS_LDO_CTRL6 0x0F +#define MENELAUS_LDO_CTRL7 0x10 +#define MENELAUS_LDO_CTRL8 0x11 +#define MENELAUS_SLEEP_CTRL1 0x12 +#define MENELAUS_SLEEP_CTRL2 0x13 +#define MENELAUS_DEVICE_OFF 0x14 +#define MENELAUS_OSC_CTRL 0x15 +#define MENELAUS_DETECT_CTRL 0x16 +#define MENELAUS_INT_MASK1 0x17 +#define MENELAUS_INT_MASK2 0x18 +#define MENELAUS_INT_STATUS1 0x19 +#define MENELAUS_INT_STATUS2 0x1A +#define MENELAUS_INT_ACK1 0x1B +#define MENELAUS_INT_ACK2 0x1C +#define MENELAUS_GPIO_CTRL 0x1D +#define MENELAUS_GPIO_IN 0x1E +#define MENELAUS_GPIO_OUT 0x1F +#define MENELAUS_BBSMS 0x20 +#define MENELAUS_RTC_CTRL 0x21 +#define MENELAUS_RTC_UPDATE 0x22 +#define MENELAUS_RTC_SEC 0x23 +#define MENELAUS_RTC_MIN 0x24 +#define MENELAUS_RTC_HR 0x25 +#define MENELAUS_RTC_DAY 0x26 +#define MENELAUS_RTC_MON 0x27 +#define MENELAUS_RTC_YR 0x28 +#define MENELAUS_RTC_WKDAY 0x29 +#define MENELAUS_RTC_AL_SEC 0x2A +#define MENELAUS_RTC_AL_MIN 0x2B +#define MENELAUS_RTC_AL_HR 0x2C +#define MENELAUS_RTC_AL_DAY 0x2D +#define MENELAUS_RTC_AL_MON 0x2E +#define MENELAUS_RTC_AL_YR 0x2F +#define MENELAUS_RTC_COMP_MSB 0x30 +#define MENELAUS_RTC_COMP_LSB 0x31 +#define MENELAUS_S1_PULL_EN 0x32 +#define MENELAUS_S1_PULL_DIR 0x33 +#define MENELAUS_S2_PULL_EN 0x34 +#define MENELAUS_S2_PULL_DIR 0x35 +#define MENELAUS_MCT_CTRL1 0x36 +#define MENELAUS_MCT_CTRL2 0x37 +#define MENELAUS_MCT_CTRL3 0x38 +#define MENELAUS_MCT_PIN_ST 0x39 +#define MENELAUS_DEBOUNCE1 0x3A + +#define IH_MENELAUS_IRQS 12 +#define MENELAUS_MMC_S1CD_IRQ 0 /* MMC slot 1 card change */ +#define MENELAUS_MMC_S2CD_IRQ 1 /* MMC slot 2 card change */ +#define MENELAUS_MMC_S1D1_IRQ 2 /* MMC DAT1 low in slot 1 */ +#define MENELAUS_MMC_S2D1_IRQ 3 /* MMC DAT1 low in slot 2 */ +#define MENELAUS_LOWBAT_IRQ 4 /* Low battery */ +#define MENELAUS_HOTDIE_IRQ 5 /* Hot die detect */ +#define MENELAUS_UVLO_IRQ 6 /* UVLO detect */ +#define MENELAUS_TSHUT_IRQ 7 /* Thermal shutdown */ +#define MENELAUS_RTCTMR_IRQ 8 /* RTC timer */ +#define MENELAUS_RTCALM_IRQ 9 /* RTC alarm */ +#define MENELAUS_RTCERR_IRQ 10 /* RTC error */ +#define MENELAUS_PSHBTN_IRQ 11 /* Push button */ +#define MENELAUS_RESERVED12_IRQ 12 /* Reserved */ +#define MENELAUS_RESERVED13_IRQ 13 /* Reserved */ +#define MENELAUS_RESERVED14_IRQ 14 /* Reserved */ +#define MENELAUS_RESERVED15_IRQ 15 /* Reserved */ + +static void menelaus_work(struct work_struct *_menelaus); + +struct menelaus_chip { + struct mutex lock; + struct i2c_client *client; + struct work_struct work; +#ifdef CONFIG_RTC_DRV_TWL92330 + struct rtc_device *rtc; + u8 rtc_control; + unsigned uie:1; +#endif + unsigned vcore_hw_mode:1; + u8 mask1, mask2; + void (*handlers[16])(struct menelaus_chip *); + void (*mmc_callback)(void *data, u8 mask); + void *mmc_callback_data; +}; + +static struct menelaus_chip *the_menelaus; + +static int menelaus_write_reg(int reg, u8 value) +{ + int val = i2c_smbus_write_byte_data(the_menelaus->client, reg, value); + + if (val < 0) { + pr_err(DRIVER_NAME ": write error"); + return val; + } + + return 0; +} + +static int menelaus_read_reg(int reg) +{ + int val = i2c_smbus_read_byte_data(the_menelaus->client, reg); + + if (val < 0) + pr_err(DRIVER_NAME ": read error"); + + return val; +} + +static int menelaus_enable_irq(int irq) +{ + if (irq > 7) { + irq -= 8; + the_menelaus->mask2 &= ~(1 << irq); + return menelaus_write_reg(MENELAUS_INT_MASK2, + the_menelaus->mask2); + } else { + the_menelaus->mask1 &= ~(1 << irq); + return menelaus_write_reg(MENELAUS_INT_MASK1, + the_menelaus->mask1); + } +} + +static int menelaus_disable_irq(int irq) +{ + if (irq > 7) { + irq -= 8; + the_menelaus->mask2 |= (1 << irq); + return menelaus_write_reg(MENELAUS_INT_MASK2, + the_menelaus->mask2); + } else { + the_menelaus->mask1 |= (1 << irq); + return menelaus_write_reg(MENELAUS_INT_MASK1, + the_menelaus->mask1); + } +} + +static int menelaus_ack_irq(int irq) +{ + if (irq > 7) + return menelaus_write_reg(MENELAUS_INT_ACK2, 1 << (irq - 8)); + else + return menelaus_write_reg(MENELAUS_INT_ACK1, 1 << irq); +} + +/* Adds a handler for an interrupt. Does not run in interrupt context */ +static int menelaus_add_irq_work(int irq, + void (*handler)(struct menelaus_chip *)) +{ + int ret = 0; + + mutex_lock(&the_menelaus->lock); + the_menelaus->handlers[irq] = handler; + ret = menelaus_enable_irq(irq); + mutex_unlock(&the_menelaus->lock); + + return ret; +} + +/* Removes handler for an interrupt */ +static int menelaus_remove_irq_work(int irq) +{ + int ret = 0; + + mutex_lock(&the_menelaus->lock); + ret = menelaus_disable_irq(irq); + the_menelaus->handlers[irq] = NULL; + mutex_unlock(&the_menelaus->lock); + + return ret; +} + +/* + * Gets scheduled when a card detect interrupt happens. Note that in some cases + * this line is wired to card cover switch rather than the card detect switch + * in each slot. In this case the cards are not seen by menelaus. + * FIXME: Add handling for D1 too + */ +static void menelaus_mmc_cd_work(struct menelaus_chip *menelaus_hw) +{ + int reg; + unsigned char card_mask = 0; + + reg = menelaus_read_reg(MENELAUS_MCT_PIN_ST); + if (reg < 0) + return; + + if (!(reg & 0x1)) + card_mask |= (1 << 0); + + if (!(reg & 0x2)) + card_mask |= (1 << 1); + + if (menelaus_hw->mmc_callback) + menelaus_hw->mmc_callback(menelaus_hw->mmc_callback_data, + card_mask); +} + +/* + * Toggles the MMC slots between open-drain and push-pull mode. + */ +int menelaus_set_mmc_opendrain(int slot, int enable) +{ + int ret, val; + + if (slot != 1 && slot != 2) + return -EINVAL; + mutex_lock(&the_menelaus->lock); + ret = menelaus_read_reg(MENELAUS_MCT_CTRL1); + if (ret < 0) { + mutex_unlock(&the_menelaus->lock); + return ret; + } + val = ret; + if (slot == 1) { + if (enable) + val |= 1 << 2; + else + val &= ~(1 << 2); + } else { + if (enable) + val |= 1 << 3; + else + val &= ~(1 << 3); + } + ret = menelaus_write_reg(MENELAUS_MCT_CTRL1, val); + mutex_unlock(&the_menelaus->lock); + + return ret; +} +EXPORT_SYMBOL(menelaus_set_mmc_opendrain); + +int menelaus_set_slot_sel(int enable) +{ + int ret; + + mutex_lock(&the_menelaus->lock); + ret = menelaus_read_reg(MENELAUS_GPIO_CTRL); + if (ret < 0) + goto out; + ret |= 0x02; + if (enable) + ret |= 1 << 5; + else + ret &= ~(1 << 5); + ret = menelaus_write_reg(MENELAUS_GPIO_CTRL, ret); +out: + mutex_unlock(&the_menelaus->lock); + return ret; +} +EXPORT_SYMBOL(menelaus_set_slot_sel); + +int menelaus_set_mmc_slot(int slot, int enable, int power, int cd_en) +{ + int ret, val; + + if (slot != 1 && slot != 2) + return -EINVAL; + if (power >= 3) + return -EINVAL; + + mutex_lock(&the_menelaus->lock); + + ret = menelaus_read_reg(MENELAUS_MCT_CTRL2); + if (ret < 0) + goto out; + val = ret; + if (slot == 1) { + if (cd_en) + val |= (1 << 4) | (1 << 6); + else + val &= ~((1 << 4) | (1 << 6)); + } else { + if (cd_en) + val |= (1 << 5) | (1 << 7); + else + val &= ~((1 << 5) | (1 << 7)); + } + ret = menelaus_write_reg(MENELAUS_MCT_CTRL2, val); + if (ret < 0) + goto out; + + ret = menelaus_read_reg(MENELAUS_MCT_CTRL3); + if (ret < 0) + goto out; + val = ret; + if (slot == 1) { + if (enable) + val |= 1 << 0; + else + val &= ~(1 << 0); + } else { + int b; + + if (enable) + ret |= 1 << 1; + else + ret &= ~(1 << 1); + b = menelaus_read_reg(MENELAUS_MCT_CTRL2); + b &= ~0x03; + b |= power; + ret = menelaus_write_reg(MENELAUS_MCT_CTRL2, b); + if (ret < 0) + goto out; + } + /* Disable autonomous shutdown */ + val &= ~(0x03 << 2); + ret = menelaus_write_reg(MENELAUS_MCT_CTRL3, val); +out: + mutex_unlock(&the_menelaus->lock); + return ret; +} +EXPORT_SYMBOL(menelaus_set_mmc_slot); + +int menelaus_register_mmc_callback(void (*callback)(void *data, u8 card_mask), + void *data) +{ + int ret = 0; + + the_menelaus->mmc_callback_data = data; + the_menelaus->mmc_callback = callback; + ret = menelaus_add_irq_work(MENELAUS_MMC_S1CD_IRQ, + menelaus_mmc_cd_work); + if (ret < 0) + return ret; + ret = menelaus_add_irq_work(MENELAUS_MMC_S2CD_IRQ, + menelaus_mmc_cd_work); + if (ret < 0) + return ret; + ret = menelaus_add_irq_work(MENELAUS_MMC_S1D1_IRQ, + menelaus_mmc_cd_work); + if (ret < 0) + return ret; + ret = menelaus_add_irq_work(MENELAUS_MMC_S2D1_IRQ, + menelaus_mmc_cd_work); + + return ret; +} +EXPORT_SYMBOL(menelaus_register_mmc_callback); + +void menelaus_unregister_mmc_callback(void) +{ + menelaus_remove_irq_work(MENELAUS_MMC_S1CD_IRQ); + menelaus_remove_irq_work(MENELAUS_MMC_S2CD_IRQ); + menelaus_remove_irq_work(MENELAUS_MMC_S1D1_IRQ); + menelaus_remove_irq_work(MENELAUS_MMC_S2D1_IRQ); + + the_menelaus->mmc_callback = NULL; + the_menelaus->mmc_callback_data = 0; +} +EXPORT_SYMBOL(menelaus_unregister_mmc_callback); + +struct menelaus_vtg { + const char *name; + u8 vtg_reg; + u8 vtg_shift; + u8 vtg_bits; + u8 mode_reg; +}; + +struct menelaus_vtg_value { + u16 vtg; + u16 val; +}; + +static int menelaus_set_voltage(const struct menelaus_vtg *vtg, int mV, + int vtg_val, int mode) +{ + int val, ret; + struct i2c_client *c = the_menelaus->client; + + mutex_lock(&the_menelaus->lock); + if (vtg == 0) + goto set_voltage; + + ret = menelaus_read_reg(vtg->vtg_reg); + if (ret < 0) + goto out; + val = ret & ~(((1 << vtg->vtg_bits) - 1) << vtg->vtg_shift); + val |= vtg_val << vtg->vtg_shift; + + dev_dbg(&c->dev, "Setting voltage '%s'" + "to %d mV (reg 0x%02x, val 0x%02x)\n", + vtg->name, mV, vtg->vtg_reg, val); + + ret = menelaus_write_reg(vtg->vtg_reg, val); + if (ret < 0) + goto out; +set_voltage: + ret = menelaus_write_reg(vtg->mode_reg, mode); +out: + mutex_unlock(&the_menelaus->lock); + if (ret == 0) { + /* Wait for voltage to stabilize */ + msleep(1); + } + return ret; +} + +static int menelaus_get_vtg_value(int vtg, const struct menelaus_vtg_value *tbl, + int n) +{ + int i; + + for (i = 0; i < n; i++, tbl++) + if (tbl->vtg == vtg) + return tbl->val; + return -EINVAL; +} + +/* + * Vcore can be programmed in two ways: + * SW-controlled: Required voltage is programmed into VCORE_CTRL1 + * HW-controlled: Required range (roof-floor) is programmed into VCORE_CTRL3 + * and VCORE_CTRL4 + * + * Call correct 'set' function accordingly + */ + +static const struct menelaus_vtg_value vcore_values[] = { + { 1000, 0 }, + { 1025, 1 }, + { 1050, 2 }, + { 1075, 3 }, + { 1100, 4 }, + { 1125, 5 }, + { 1150, 6 }, + { 1175, 7 }, + { 1200, 8 }, + { 1225, 9 }, + { 1250, 10 }, + { 1275, 11 }, + { 1300, 12 }, + { 1325, 13 }, + { 1350, 14 }, + { 1375, 15 }, + { 1400, 16 }, + { 1425, 17 }, + { 1450, 18 }, +}; + +int menelaus_set_vcore_sw(unsigned int mV) +{ + int val, ret; + struct i2c_client *c = the_menelaus->client; + + val = menelaus_get_vtg_value(mV, vcore_values, + ARRAY_SIZE(vcore_values)); + if (val < 0) + return -EINVAL; + + dev_dbg(&c->dev, "Setting VCORE to %d mV (val 0x%02x)\n", mV, val); + + /* Set SW mode and the voltage in one go. */ + mutex_lock(&the_menelaus->lock); + ret = menelaus_write_reg(MENELAUS_VCORE_CTRL1, val); + if (ret == 0) + the_menelaus->vcore_hw_mode = 0; + mutex_unlock(&the_menelaus->lock); + msleep(1); + + return ret; +} + +int menelaus_set_vcore_hw(unsigned int roof_mV, unsigned int floor_mV) +{ + int fval, rval, val, ret; + struct i2c_client *c = the_menelaus->client; + + rval = menelaus_get_vtg_value(roof_mV, vcore_values, + ARRAY_SIZE(vcore_values)); + if (rval < 0) + return -EINVAL; + fval = menelaus_get_vtg_value(floor_mV, vcore_values, + ARRAY_SIZE(vcore_values)); + if (fval < 0) + return -EINVAL; + + dev_dbg(&c->dev, "Setting VCORE FLOOR to %d mV and ROOF to %d mV\n", + floor_mV, roof_mV); + + mutex_lock(&the_menelaus->lock); + ret = menelaus_write_reg(MENELAUS_VCORE_CTRL3, fval); + if (ret < 0) + goto out; + ret = menelaus_write_reg(MENELAUS_VCORE_CTRL4, rval); + if (ret < 0) + goto out; + if (!the_menelaus->vcore_hw_mode) { + val = menelaus_read_reg(MENELAUS_VCORE_CTRL1); + /* HW mode, turn OFF byte comparator */ + val |= ((1 << 7) | (1 << 5)); + ret = menelaus_write_reg(MENELAUS_VCORE_CTRL1, val); + the_menelaus->vcore_hw_mode = 1; + } + msleep(1); +out: + mutex_unlock(&the_menelaus->lock); + return ret; +} + +static const struct menelaus_vtg vmem_vtg = { + .name = "VMEM", + .vtg_reg = MENELAUS_LDO_CTRL1, + .vtg_shift = 0, + .vtg_bits = 2, + .mode_reg = MENELAUS_LDO_CTRL3, +}; + +static const struct menelaus_vtg_value vmem_values[] = { + { 1500, 0 }, + { 1800, 1 }, + { 1900, 2 }, + { 2500, 3 }, +}; + +int menelaus_set_vmem(unsigned int mV) +{ + int val; + + if (mV == 0) + return menelaus_set_voltage(&vmem_vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vmem_values, ARRAY_SIZE(vmem_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(&vmem_vtg, mV, val, 0x02); +} +EXPORT_SYMBOL(menelaus_set_vmem); + +static const struct menelaus_vtg vio_vtg = { + .name = "VIO", + .vtg_reg = MENELAUS_LDO_CTRL1, + .vtg_shift = 2, + .vtg_bits = 2, + .mode_reg = MENELAUS_LDO_CTRL4, +}; + +static const struct menelaus_vtg_value vio_values[] = { + { 1500, 0 }, + { 1800, 1 }, + { 2500, 2 }, + { 2800, 3 }, +}; + +int menelaus_set_vio(unsigned int mV) +{ + int val; + + if (mV == 0) + return menelaus_set_voltage(&vio_vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vio_values, ARRAY_SIZE(vio_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(&vio_vtg, mV, val, 0x02); +} +EXPORT_SYMBOL(menelaus_set_vio); + +static const struct menelaus_vtg_value vdcdc_values[] = { + { 1500, 0 }, + { 1800, 1 }, + { 2000, 2 }, + { 2200, 3 }, + { 2400, 4 }, + { 2800, 5 }, + { 3000, 6 }, + { 3300, 7 }, +}; + +static const struct menelaus_vtg vdcdc2_vtg = { + .name = "VDCDC2", + .vtg_reg = MENELAUS_DCDC_CTRL1, + .vtg_shift = 0, + .vtg_bits = 3, + .mode_reg = MENELAUS_DCDC_CTRL2, +}; + +static const struct menelaus_vtg vdcdc3_vtg = { + .name = "VDCDC3", + .vtg_reg = MENELAUS_DCDC_CTRL1, + .vtg_shift = 3, + .vtg_bits = 3, + .mode_reg = MENELAUS_DCDC_CTRL3, +}; + +int menelaus_set_vdcdc(int dcdc, unsigned int mV) +{ + const struct menelaus_vtg *vtg; + int val; + + if (dcdc != 2 && dcdc != 3) + return -EINVAL; + if (dcdc == 2) + vtg = &vdcdc2_vtg; + else + vtg = &vdcdc3_vtg; + + if (mV == 0) + return menelaus_set_voltage(vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vdcdc_values, + ARRAY_SIZE(vdcdc_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(vtg, mV, val, 0x03); +} + +static const struct menelaus_vtg_value vmmc_values[] = { + { 1850, 0 }, + { 2800, 1 }, + { 3000, 2 }, + { 3100, 3 }, +}; + +static const struct menelaus_vtg vmmc_vtg = { + .name = "VMMC", + .vtg_reg = MENELAUS_LDO_CTRL1, + .vtg_shift = 6, + .vtg_bits = 2, + .mode_reg = MENELAUS_LDO_CTRL7, +}; + +int menelaus_set_vmmc(unsigned int mV) +{ + int val; + + if (mV == 0) + return menelaus_set_voltage(&vmmc_vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vmmc_values, ARRAY_SIZE(vmmc_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(&vmmc_vtg, mV, val, 0x02); +} +EXPORT_SYMBOL(menelaus_set_vmmc); + + +static const struct menelaus_vtg_value vaux_values[] = { + { 1500, 0 }, + { 1800, 1 }, + { 2500, 2 }, + { 2800, 3 }, +}; + +static const struct menelaus_vtg vaux_vtg = { + .name = "VAUX", + .vtg_reg = MENELAUS_LDO_CTRL1, + .vtg_shift = 4, + .vtg_bits = 2, + .mode_reg = MENELAUS_LDO_CTRL6, +}; + +int menelaus_set_vaux(unsigned int mV) +{ + int val; + + if (mV == 0) + return menelaus_set_voltage(&vaux_vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vaux_values, ARRAY_SIZE(vaux_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(&vaux_vtg, mV, val, 0x02); +} +EXPORT_SYMBOL(menelaus_set_vaux); + +int menelaus_get_slot_pin_states(void) +{ + return menelaus_read_reg(MENELAUS_MCT_PIN_ST); +} +EXPORT_SYMBOL(menelaus_get_slot_pin_states); + +int menelaus_set_regulator_sleep(int enable, u32 val) +{ + int t, ret; + struct i2c_client *c = the_menelaus->client; + + mutex_lock(&the_menelaus->lock); + ret = menelaus_write_reg(MENELAUS_SLEEP_CTRL2, val); + if (ret < 0) + goto out; + + dev_dbg(&c->dev, "regulator sleep configuration: %02x\n", val); + + ret = menelaus_read_reg(MENELAUS_GPIO_CTRL); + if (ret < 0) + goto out; + t = ((1 << 6) | 0x04); + if (enable) + ret |= t; + else + ret &= ~t; + ret = menelaus_write_reg(MENELAUS_GPIO_CTRL, ret); +out: + mutex_unlock(&the_menelaus->lock); + return ret; +} + +/*-----------------------------------------------------------------------*/ + +/* Handles Menelaus interrupts. Does not run in interrupt context */ +static void menelaus_work(struct work_struct *_menelaus) +{ + struct menelaus_chip *menelaus = + container_of(_menelaus, struct menelaus_chip, work); + void (*handler)(struct menelaus_chip *menelaus); + + while (1) { + unsigned isr; + + isr = (menelaus_read_reg(MENELAUS_INT_STATUS2) + & ~menelaus->mask2) << 8; + isr |= menelaus_read_reg(MENELAUS_INT_STATUS1) + & ~menelaus->mask1; + if (!isr) + break; + + while (isr) { + int irq = fls(isr) - 1; + isr &= ~(1 << irq); + + mutex_lock(&menelaus->lock); + menelaus_disable_irq(irq); + menelaus_ack_irq(irq); + handler = menelaus->handlers[irq]; + if (handler) + handler(menelaus); + menelaus_enable_irq(irq); + mutex_unlock(&menelaus->lock); + } + } + enable_irq(menelaus->client->irq); +} + +/* + * We cannot use I2C in interrupt context, so we just schedule work. + */ +static irqreturn_t menelaus_irq(int irq, void *_menelaus) +{ + struct menelaus_chip *menelaus = _menelaus; + + disable_irq_nosync(irq); + (void)schedule_work(&menelaus->work); + + return IRQ_HANDLED; +} + +/*-----------------------------------------------------------------------*/ + +/* + * The RTC needs to be set once, then it runs on backup battery power. + * It supports alarms, including system wake alarms (from some modes); + * and 1/second IRQs if requested. + */ +#ifdef CONFIG_RTC_DRV_TWL92330 + +#define RTC_CTRL_RTC_EN (1 << 0) +#define RTC_CTRL_AL_EN (1 << 1) +#define RTC_CTRL_MODE12 (1 << 2) +#define RTC_CTRL_EVERY_MASK (3 << 3) +#define RTC_CTRL_EVERY_SEC (0 << 3) +#define RTC_CTRL_EVERY_MIN (1 << 3) +#define RTC_CTRL_EVERY_HR (2 << 3) +#define RTC_CTRL_EVERY_DAY (3 << 3) + +#define RTC_UPDATE_EVERY 0x08 + +#define RTC_HR_PM (1 << 7) + +static void menelaus_to_time(char *regs, struct rtc_time *t) +{ + t->tm_sec = bcd2bin(regs[0]); + t->tm_min = bcd2bin(regs[1]); + if (the_menelaus->rtc_control & RTC_CTRL_MODE12) { + t->tm_hour = bcd2bin(regs[2] & 0x1f) - 1; + if (regs[2] & RTC_HR_PM) + t->tm_hour += 12; + } else + t->tm_hour = bcd2bin(regs[2] & 0x3f); + t->tm_mday = bcd2bin(regs[3]); + t->tm_mon = bcd2bin(regs[4]) - 1; + t->tm_year = bcd2bin(regs[5]) + 100; +} + +static int time_to_menelaus(struct rtc_time *t, int regnum) +{ + int hour, status; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_sec)); + if (status < 0) + goto fail; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_min)); + if (status < 0) + goto fail; + + if (the_menelaus->rtc_control & RTC_CTRL_MODE12) { + hour = t->tm_hour + 1; + if (hour > 12) + hour = RTC_HR_PM | bin2bcd(hour - 12); + else + hour = bin2bcd(hour); + } else + hour = bin2bcd(t->tm_hour); + status = menelaus_write_reg(regnum++, hour); + if (status < 0) + goto fail; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_mday)); + if (status < 0) + goto fail; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_mon + 1)); + if (status < 0) + goto fail; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_year - 100)); + if (status < 0) + goto fail; + + return 0; +fail: + dev_err(&the_menelaus->client->dev, "rtc write reg %02x, err %d\n", + --regnum, status); + return status; +} + +static int menelaus_read_time(struct device *dev, struct rtc_time *t) +{ + struct i2c_msg msg[2]; + char regs[7]; + int status; + + /* block read date and time registers */ + regs[0] = MENELAUS_RTC_SEC; + + msg[0].addr = MENELAUS_I2C_ADDRESS; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = regs; + + msg[1].addr = MENELAUS_I2C_ADDRESS; + msg[1].flags = I2C_M_RD; + msg[1].len = sizeof(regs); + msg[1].buf = regs; + + status = i2c_transfer(the_menelaus->client->adapter, msg, 2); + if (status != 2) { + dev_err(dev, "%s error %d\n", "read", status); + return -EIO; + } + + menelaus_to_time(regs, t); + t->tm_wday = bcd2bin(regs[6]); + + return 0; +} + +static int menelaus_set_time(struct device *dev, struct rtc_time *t) +{ + int status; + + /* write date and time registers */ + status = time_to_menelaus(t, MENELAUS_RTC_SEC); + if (status < 0) + return status; + status = menelaus_write_reg(MENELAUS_RTC_WKDAY, bin2bcd(t->tm_wday)); + if (status < 0) { + dev_err(&the_menelaus->client->dev, "rtc write reg %02x " + "err %d\n", MENELAUS_RTC_WKDAY, status); + return status; + } + + /* now commit the write */ + status = menelaus_write_reg(MENELAUS_RTC_UPDATE, RTC_UPDATE_EVERY); + if (status < 0) + dev_err(&the_menelaus->client->dev, "rtc commit time, err %d\n", + status); + + return 0; +} + +static int menelaus_read_alarm(struct device *dev, struct rtc_wkalrm *w) +{ + struct i2c_msg msg[2]; + char regs[6]; + int status; + + /* block read alarm registers */ + regs[0] = MENELAUS_RTC_AL_SEC; + + msg[0].addr = MENELAUS_I2C_ADDRESS; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = regs; + + msg[1].addr = MENELAUS_I2C_ADDRESS; + msg[1].flags = I2C_M_RD; + msg[1].len = sizeof(regs); + msg[1].buf = regs; + + status = i2c_transfer(the_menelaus->client->adapter, msg, 2); + if (status != 2) { + dev_err(dev, "%s error %d\n", "alarm read", status); + return -EIO; + } + + menelaus_to_time(regs, &w->time); + + w->enabled = !!(the_menelaus->rtc_control & RTC_CTRL_AL_EN); + + /* NOTE we *could* check if actually pending... */ + w->pending = 0; + + return 0; +} + +static int menelaus_set_alarm(struct device *dev, struct rtc_wkalrm *w) +{ + int status; + + if (the_menelaus->client->irq <= 0 && w->enabled) + return -ENODEV; + + /* clear previous alarm enable */ + if (the_menelaus->rtc_control & RTC_CTRL_AL_EN) { + the_menelaus->rtc_control &= ~RTC_CTRL_AL_EN; + status = menelaus_write_reg(MENELAUS_RTC_CTRL, + the_menelaus->rtc_control); + if (status < 0) + return status; + } + + /* write alarm registers */ + status = time_to_menelaus(&w->time, MENELAUS_RTC_AL_SEC); + if (status < 0) + return status; + + /* enable alarm if requested */ + if (w->enabled) { + the_menelaus->rtc_control |= RTC_CTRL_AL_EN; + status = menelaus_write_reg(MENELAUS_RTC_CTRL, + the_menelaus->rtc_control); + } + + return status; +} + +#ifdef CONFIG_RTC_INTF_DEV + +static void menelaus_rtc_update_work(struct menelaus_chip *m) +{ + /* report 1/sec update */ + local_irq_disable(); + rtc_update_irq(m->rtc, 1, RTC_IRQF | RTC_UF); + local_irq_enable(); +} + +static int menelaus_ioctl(struct device *dev, unsigned cmd, unsigned long arg) +{ + int status; + + if (the_menelaus->client->irq <= 0) + return -ENOIOCTLCMD; + + switch (cmd) { + /* alarm IRQ */ + case RTC_AIE_ON: + if (the_menelaus->rtc_control & RTC_CTRL_AL_EN) + return 0; + the_menelaus->rtc_control |= RTC_CTRL_AL_EN; + break; + case RTC_AIE_OFF: + if (!(the_menelaus->rtc_control & RTC_CTRL_AL_EN)) + return 0; + the_menelaus->rtc_control &= ~RTC_CTRL_AL_EN; + break; + /* 1/second "update" IRQ */ + case RTC_UIE_ON: + if (the_menelaus->uie) + return 0; + status = menelaus_remove_irq_work(MENELAUS_RTCTMR_IRQ); + status = menelaus_add_irq_work(MENELAUS_RTCTMR_IRQ, + menelaus_rtc_update_work); + if (status == 0) + the_menelaus->uie = 1; + return status; + case RTC_UIE_OFF: + if (!the_menelaus->uie) + return 0; + status = menelaus_remove_irq_work(MENELAUS_RTCTMR_IRQ); + if (status == 0) + the_menelaus->uie = 0; + return status; + default: + return -ENOIOCTLCMD; + } + return menelaus_write_reg(MENELAUS_RTC_CTRL, the_menelaus->rtc_control); +} + +#else +#define menelaus_ioctl NULL +#endif + +/* REVISIT no compensation register support ... */ + +static const struct rtc_class_ops menelaus_rtc_ops = { + .ioctl = menelaus_ioctl, + .read_time = menelaus_read_time, + .set_time = menelaus_set_time, + .read_alarm = menelaus_read_alarm, + .set_alarm = menelaus_set_alarm, +}; + +static void menelaus_rtc_alarm_work(struct menelaus_chip *m) +{ + /* report alarm */ + local_irq_disable(); + rtc_update_irq(m->rtc, 1, RTC_IRQF | RTC_AF); + local_irq_enable(); + + /* then disable it; alarms are oneshot */ + the_menelaus->rtc_control &= ~RTC_CTRL_AL_EN; + menelaus_write_reg(MENELAUS_RTC_CTRL, the_menelaus->rtc_control); +} + +static inline void menelaus_rtc_init(struct menelaus_chip *m) +{ + int alarm = (m->client->irq > 0); + + /* assume 32KDETEN pin is pulled high */ + if (!(menelaus_read_reg(MENELAUS_OSC_CTRL) & 0x80)) { + dev_dbg(&m->client->dev, "no 32k oscillator\n"); + return; + } + + /* support RTC alarm; it can issue wakeups */ + if (alarm) { + if (menelaus_add_irq_work(MENELAUS_RTCALM_IRQ, + menelaus_rtc_alarm_work) < 0) { + dev_err(&m->client->dev, "can't handle RTC alarm\n"); + return; + } + device_init_wakeup(&m->client->dev, 1); + } + + /* be sure RTC is enabled; allow 1/sec irqs; leave 12hr mode alone */ + m->rtc_control = menelaus_read_reg(MENELAUS_RTC_CTRL); + if (!(m->rtc_control & RTC_CTRL_RTC_EN) + || (m->rtc_control & RTC_CTRL_AL_EN) + || (m->rtc_control & RTC_CTRL_EVERY_MASK)) { + if (!(m->rtc_control & RTC_CTRL_RTC_EN)) { + dev_warn(&m->client->dev, "rtc clock needs setting\n"); + m->rtc_control |= RTC_CTRL_RTC_EN; + } + m->rtc_control &= ~RTC_CTRL_EVERY_MASK; + m->rtc_control &= ~RTC_CTRL_AL_EN; + menelaus_write_reg(MENELAUS_RTC_CTRL, m->rtc_control); + } + + m->rtc = rtc_device_register(DRIVER_NAME, + &m->client->dev, + &menelaus_rtc_ops, THIS_MODULE); + if (IS_ERR(m->rtc)) { + if (alarm) { + menelaus_remove_irq_work(MENELAUS_RTCALM_IRQ); + device_init_wakeup(&m->client->dev, 0); + } + dev_err(&m->client->dev, "can't register RTC: %d\n", + (int) PTR_ERR(m->rtc)); + the_menelaus->rtc = NULL; + } +} + +#else + +static inline void menelaus_rtc_init(struct menelaus_chip *m) +{ + /* nothing */ +} + +#endif + +/*-----------------------------------------------------------------------*/ + +static struct i2c_driver menelaus_i2c_driver; + +static int menelaus_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct menelaus_chip *menelaus; + int rev = 0, val; + int err = 0; + struct menelaus_platform_data *menelaus_pdata = + client->dev.platform_data; + + if (the_menelaus) { + dev_dbg(&client->dev, "only one %s for now\n", + DRIVER_NAME); + return -ENODEV; + } + + menelaus = kzalloc(sizeof *menelaus, GFP_KERNEL); + if (!menelaus) + return -ENOMEM; + + i2c_set_clientdata(client, menelaus); + + the_menelaus = menelaus; + menelaus->client = client; + + /* If a true probe check the device */ + rev = menelaus_read_reg(MENELAUS_REV); + if (rev < 0) { + pr_err(DRIVER_NAME ": device not found"); + err = -ENODEV; + goto fail1; + } + + /* Ack and disable all Menelaus interrupts */ + menelaus_write_reg(MENELAUS_INT_ACK1, 0xff); + menelaus_write_reg(MENELAUS_INT_ACK2, 0xff); + menelaus_write_reg(MENELAUS_INT_MASK1, 0xff); + menelaus_write_reg(MENELAUS_INT_MASK2, 0xff); + menelaus->mask1 = 0xff; + menelaus->mask2 = 0xff; + + /* Set output buffer strengths */ + menelaus_write_reg(MENELAUS_MCT_CTRL1, 0x73); + + if (client->irq > 0) { + err = request_irq(client->irq, menelaus_irq, IRQF_DISABLED, + DRIVER_NAME, menelaus); + if (err) { + dev_dbg(&client->dev, "can't get IRQ %d, err %d\n", + client->irq, err); + goto fail1; + } + } + + mutex_init(&menelaus->lock); + INIT_WORK(&menelaus->work, menelaus_work); + + pr_info("Menelaus rev %d.%d\n", rev >> 4, rev & 0x0f); + + val = menelaus_read_reg(MENELAUS_VCORE_CTRL1); + if (val < 0) + goto fail2; + if (val & (1 << 7)) + menelaus->vcore_hw_mode = 1; + else + menelaus->vcore_hw_mode = 0; + + if (menelaus_pdata != NULL && menelaus_pdata->late_init != NULL) { + err = menelaus_pdata->late_init(&client->dev); + if (err < 0) + goto fail2; + } + + menelaus_rtc_init(menelaus); + + return 0; +fail2: + free_irq(client->irq, menelaus); + flush_scheduled_work(); +fail1: + kfree(menelaus); + return err; +} + +static int __exit menelaus_remove(struct i2c_client *client) +{ + struct menelaus_chip *menelaus = i2c_get_clientdata(client); + + free_irq(client->irq, menelaus); + kfree(menelaus); + i2c_set_clientdata(client, NULL); + the_menelaus = NULL; + return 0; +} + +static const struct i2c_device_id menelaus_id[] = { + { "menelaus", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, menelaus_id); + +static struct i2c_driver menelaus_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = menelaus_probe, + .remove = __exit_p(menelaus_remove), + .id_table = menelaus_id, +}; + +static int __init menelaus_init(void) +{ + int res; + + res = i2c_add_driver(&menelaus_i2c_driver); + if (res < 0) { + pr_err(DRIVER_NAME ": driver registration failed\n"); + return res; + } + + return 0; +} + +static void __exit menelaus_exit(void) +{ + i2c_del_driver(&menelaus_i2c_driver); + + /* FIXME: Shutdown menelaus parts that can be shut down */ +} + +MODULE_AUTHOR("Texas Instruments, Inc. (and others)"); +MODULE_DESCRIPTION("I2C interface for Menelaus."); +MODULE_LICENSE("GPL"); + +module_init(menelaus_init); +module_exit(menelaus_exit); diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c new file mode 100644 index 00000000000..ae15e495e20 --- /dev/null +++ b/drivers/mfd/mfd-core.c @@ -0,0 +1,120 @@ +/* + * drivers/mfd/mfd-core.c + * + * core MFD support + * Copyright (c) 2006 Ian Molton + * Copyright (c) 2007,2008 Dmitry Baryshkov + * + * 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 <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> + +static int mfd_add_device(struct device *parent, int id, + const struct mfd_cell *cell, + struct resource *mem_base, + int irq_base) +{ + struct resource *res; + struct platform_device *pdev; + int ret = -ENOMEM; + int r; + + pdev = platform_device_alloc(cell->name, id + cell->id); + if (!pdev) + goto fail_alloc; + + res = kzalloc(sizeof(*res) * cell->num_resources, GFP_KERNEL); + if (!res) + goto fail_device; + + pdev->dev.parent = parent; + platform_set_drvdata(pdev, cell->driver_data); + + ret = platform_device_add_data(pdev, + cell->platform_data, cell->data_size); + if (ret) + goto fail_res; + + for (r = 0; r < cell->num_resources; r++) { + res[r].name = cell->resources[r].name; + res[r].flags = cell->resources[r].flags; + + /* Find out base to use */ + if (cell->resources[r].flags & IORESOURCE_MEM) { + res[r].parent = mem_base; + res[r].start = mem_base->start + + cell->resources[r].start; + res[r].end = mem_base->start + + cell->resources[r].end; + } else if (cell->resources[r].flags & IORESOURCE_IRQ) { + res[r].start = irq_base + + cell->resources[r].start; + res[r].end = irq_base + + cell->resources[r].end; + } else { + res[r].parent = cell->resources[r].parent; + res[r].start = cell->resources[r].start; + res[r].end = cell->resources[r].end; + } + } + + platform_device_add_resources(pdev, res, cell->num_resources); + + ret = platform_device_add(pdev); + if (ret) + goto fail_res; + + kfree(res); + + return 0; + +/* platform_device_del(pdev); */ +fail_res: + kfree(res); +fail_device: + platform_device_put(pdev); +fail_alloc: + return ret; +} + +int mfd_add_devices(struct device *parent, int id, + const struct mfd_cell *cells, int n_devs, + struct resource *mem_base, + int irq_base) +{ + int i; + int ret = 0; + + for (i = 0; i < n_devs; i++) { + ret = mfd_add_device(parent, id, cells + i, mem_base, irq_base); + if (ret) + break; + } + + if (ret) + mfd_remove_devices(parent); + + return ret; +} +EXPORT_SYMBOL(mfd_add_devices); + +static int mfd_remove_devices_fn(struct device *dev, void *unused) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +void mfd_remove_devices(struct device *parent) +{ + device_for_each_child(parent, NULL, mfd_remove_devices_fn); +} +EXPORT_SYMBOL(mfd_remove_devices); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov"); diff --git a/drivers/mfd/pcf50633-adc.c b/drivers/mfd/pcf50633-adc.c new file mode 100644 index 00000000000..6d2e8466df1 --- /dev/null +++ b/drivers/mfd/pcf50633-adc.c @@ -0,0 +1,276 @@ +/* NXP PCF50633 ADC Driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Balaji Rao <balajirrao@openmoko.org> + * All rights reserved. + * + * Broken down from monstrous PCF50633 driver mainly by + * Harald Welte, Andy Green and Werner Almesberger + * + * 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. + * + * NOTE: This driver does not yet support subtractive ADC mode, which means + * you can do only one measurement per read request. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/completion.h> + +#include <linux/mfd/pcf50633/core.h> +#include <linux/mfd/pcf50633/adc.h> + +struct pcf50633_adc_request { + int mux; + int avg; + int result; + void (*callback)(struct pcf50633 *, void *, int); + void *callback_param; + + /* Used in case of sync requests */ + struct completion completion; + +}; + +#define PCF50633_MAX_ADC_FIFO_DEPTH 8 + +struct pcf50633_adc { + struct pcf50633 *pcf; + + /* Private stuff */ + struct pcf50633_adc_request *queue[PCF50633_MAX_ADC_FIFO_DEPTH]; + int queue_head; + int queue_tail; + struct mutex queue_mutex; +}; + +static inline struct pcf50633_adc *__to_adc(struct pcf50633 *pcf) +{ + return platform_get_drvdata(pcf->adc_pdev); +} + +static void adc_setup(struct pcf50633 *pcf, int channel, int avg) +{ + channel &= PCF50633_ADCC1_ADCMUX_MASK; + + /* kill ratiometric, but enable ACCSW biasing */ + pcf50633_reg_write(pcf, PCF50633_REG_ADCC2, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_ADCC3, 0x01); + + /* start ADC conversion on selected channel */ + pcf50633_reg_write(pcf, PCF50633_REG_ADCC1, channel | avg | + PCF50633_ADCC1_ADCSTART | PCF50633_ADCC1_RES_10BIT); +} + +static void trigger_next_adc_job_if_any(struct pcf50633 *pcf) +{ + struct pcf50633_adc *adc = __to_adc(pcf); + int head; + + head = adc->queue_head; + + if (!adc->queue[head]) + return; + + adc_setup(pcf, adc->queue[head]->mux, adc->queue[head]->avg); +} + +static int +adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req) +{ + struct pcf50633_adc *adc = __to_adc(pcf); + int head, tail; + + mutex_lock(&adc->queue_mutex); + + head = adc->queue_head; + tail = adc->queue_tail; + + if (adc->queue[tail]) { + mutex_unlock(&adc->queue_mutex); + dev_err(pcf->dev, "ADC queue is full, dropping request\n"); + return -EBUSY; + } + + adc->queue[tail] = req; + if (head == tail) + trigger_next_adc_job_if_any(pcf); + adc->queue_tail = (tail + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1); + + mutex_unlock(&adc->queue_mutex); + + return 0; +} + +static void +pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result) +{ + struct pcf50633_adc_request *req = param; + + req->result = result; + complete(&req->completion); +} + +int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg) +{ + struct pcf50633_adc_request *req; + int err; + + /* req is freed when the result is ready, in interrupt handler */ + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->mux = mux; + req->avg = avg; + req->callback = pcf50633_adc_sync_read_callback; + req->callback_param = req; + + init_completion(&req->completion); + err = adc_enqueue_request(pcf, req); + if (err) + return err; + + wait_for_completion(&req->completion); + + /* FIXME by this time req might be already freed */ + return req->result; +} +EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read); + +int pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg, + void (*callback)(struct pcf50633 *, void *, int), + void *callback_param) +{ + struct pcf50633_adc_request *req; + + /* req is freed when the result is ready, in interrupt handler */ + req = kmalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->mux = mux; + req->avg = avg; + req->callback = callback; + req->callback_param = callback_param; + + return adc_enqueue_request(pcf, req); +} +EXPORT_SYMBOL_GPL(pcf50633_adc_async_read); + +static int adc_result(struct pcf50633 *pcf) +{ + u8 adcs1, adcs3; + u16 result; + + adcs1 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS1); + adcs3 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS3); + result = (adcs1 << 2) | (adcs3 & PCF50633_ADCS3_ADCDAT1L_MASK); + + dev_dbg(pcf->dev, "adc result = %d\n", result); + + return result; +} + +static void pcf50633_adc_irq(int irq, void *data) +{ + struct pcf50633_adc *adc = data; + struct pcf50633 *pcf = adc->pcf; + struct pcf50633_adc_request *req; + int head, res; + + mutex_lock(&adc->queue_mutex); + head = adc->queue_head; + + req = adc->queue[head]; + if (WARN_ON(!req)) { + dev_err(pcf->dev, "pcf50633-adc irq: ADC queue empty!\n"); + mutex_unlock(&adc->queue_mutex); + return; + } + adc->queue[head] = NULL; + adc->queue_head = (head + 1) & + (PCF50633_MAX_ADC_FIFO_DEPTH - 1); + + res = adc_result(pcf); + trigger_next_adc_job_if_any(pcf); + + mutex_unlock(&adc->queue_mutex); + + req->callback(pcf, req->callback_param, res); + kfree(req); +} + +static int __devinit pcf50633_adc_probe(struct platform_device *pdev) +{ + struct pcf50633_adc *adc; + + adc = kzalloc(sizeof(*adc), GFP_KERNEL); + if (!adc) + return -ENOMEM; + + adc->pcf = dev_to_pcf50633(pdev->dev.parent); + platform_set_drvdata(pdev, adc); + + pcf50633_register_irq(adc->pcf, PCF50633_IRQ_ADCRDY, + pcf50633_adc_irq, adc); + + mutex_init(&adc->queue_mutex); + + return 0; +} + +static int __devexit pcf50633_adc_remove(struct platform_device *pdev) +{ + struct pcf50633_adc *adc = platform_get_drvdata(pdev); + int i, head; + + pcf50633_free_irq(adc->pcf, PCF50633_IRQ_ADCRDY); + + mutex_lock(&adc->queue_mutex); + head = adc->queue_head; + + if (WARN_ON(adc->queue[head])) + dev_err(adc->pcf->dev, + "adc driver removed with request pending\n"); + + for (i = 0; i < PCF50633_MAX_ADC_FIFO_DEPTH; i++) + kfree(adc->queue[i]); + + mutex_unlock(&adc->queue_mutex); + kfree(adc); + + return 0; +} + +static struct platform_driver pcf50633_adc_driver = { + .driver = { + .name = "pcf50633-adc", + }, + .probe = pcf50633_adc_probe, + .remove = __devexit_p(pcf50633_adc_remove), +}; + +static int __init pcf50633_adc_init(void) +{ + return platform_driver_register(&pcf50633_adc_driver); +} +module_init(pcf50633_adc_init); + +static void __exit pcf50633_adc_exit(void) +{ + platform_driver_unregister(&pcf50633_adc_driver); +} +module_exit(pcf50633_adc_exit); + +MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>"); +MODULE_DESCRIPTION("PCF50633 adc driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcf50633-adc"); + diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c new file mode 100644 index 00000000000..03dcc920070 --- /dev/null +++ b/drivers/mfd/pcf50633-core.c @@ -0,0 +1,714 @@ +/* NXP PCF50633 Power Management Unit (PMU) driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * Balaji Rao <balajirrao@openmoko.org> + * All rights reserved. + * + * 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 <linux/kernel.h> +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/irq.h> + +#include <linux/mfd/pcf50633/core.h> + +/* Two MBCS registers used during cold start */ +#define PCF50633_REG_MBCS1 0x4b +#define PCF50633_REG_MBCS2 0x4c +#define PCF50633_MBCS1_USBPRES 0x01 +#define PCF50633_MBCS1_ADAPTPRES 0x01 + +static int __pcf50633_read(struct pcf50633 *pcf, u8 reg, int num, u8 *data) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(pcf->i2c_client, reg, + num, data); + if (ret < 0) + dev_err(pcf->dev, "Error reading %d regs at %d\n", num, reg); + + return ret; +} + +static int __pcf50633_write(struct pcf50633 *pcf, u8 reg, int num, u8 *data) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(pcf->i2c_client, reg, + num, data); + if (ret < 0) + dev_err(pcf->dev, "Error writing %d regs at %d\n", num, reg); + + return ret; + +} + +/* Read a block of upto 32 regs */ +int pcf50633_read_block(struct pcf50633 *pcf, u8 reg, + int nr_regs, u8 *data) +{ + int ret; + + mutex_lock(&pcf->lock); + ret = __pcf50633_read(pcf, reg, nr_regs, data); + mutex_unlock(&pcf->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(pcf50633_read_block); + +/* Write a block of upto 32 regs */ +int pcf50633_write_block(struct pcf50633 *pcf , u8 reg, + int nr_regs, u8 *data) +{ + int ret; + + mutex_lock(&pcf->lock); + ret = __pcf50633_write(pcf, reg, nr_regs, data); + mutex_unlock(&pcf->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(pcf50633_write_block); + +u8 pcf50633_reg_read(struct pcf50633 *pcf, u8 reg) +{ + u8 val; + + mutex_lock(&pcf->lock); + __pcf50633_read(pcf, reg, 1, &val); + mutex_unlock(&pcf->lock); + + return val; +} +EXPORT_SYMBOL_GPL(pcf50633_reg_read); + +int pcf50633_reg_write(struct pcf50633 *pcf, u8 reg, u8 val) +{ + int ret; + + mutex_lock(&pcf->lock); + ret = __pcf50633_write(pcf, reg, 1, &val); + mutex_unlock(&pcf->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(pcf50633_reg_write); + +int pcf50633_reg_set_bit_mask(struct pcf50633 *pcf, u8 reg, u8 mask, u8 val) +{ + int ret; + u8 tmp; + + val &= mask; + + mutex_lock(&pcf->lock); + ret = __pcf50633_read(pcf, reg, 1, &tmp); + if (ret < 0) + goto out; + + tmp &= ~mask; + tmp |= val; + ret = __pcf50633_write(pcf, reg, 1, &tmp); + +out: + mutex_unlock(&pcf->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(pcf50633_reg_set_bit_mask); + +int pcf50633_reg_clear_bits(struct pcf50633 *pcf, u8 reg, u8 val) +{ + int ret; + u8 tmp; + + mutex_lock(&pcf->lock); + ret = __pcf50633_read(pcf, reg, 1, &tmp); + if (ret < 0) + goto out; + + tmp &= ~val; + ret = __pcf50633_write(pcf, reg, 1, &tmp); + +out: + mutex_unlock(&pcf->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(pcf50633_reg_clear_bits); + +/* sysfs attributes */ +static ssize_t show_dump_regs(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pcf50633 *pcf = dev_get_drvdata(dev); + u8 dump[16]; + int n, n1, idx = 0; + char *buf1 = buf; + static u8 address_no_read[] = { /* must be ascending */ + PCF50633_REG_INT1, + PCF50633_REG_INT2, + PCF50633_REG_INT3, + PCF50633_REG_INT4, + PCF50633_REG_INT5, + 0 /* terminator */ + }; + + for (n = 0; n < 256; n += sizeof(dump)) { + for (n1 = 0; n1 < sizeof(dump); n1++) + if (n == address_no_read[idx]) { + idx++; + dump[n1] = 0x00; + } else + dump[n1] = pcf50633_reg_read(pcf, n + n1); + + hex_dump_to_buffer(dump, sizeof(dump), 16, 1, buf1, 128, 0); + buf1 += strlen(buf1); + *buf1++ = '\n'; + *buf1 = '\0'; + } + + return buf1 - buf; +} +static DEVICE_ATTR(dump_regs, 0400, show_dump_regs, NULL); + +static ssize_t show_resume_reason(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pcf50633 *pcf = dev_get_drvdata(dev); + int n; + + n = sprintf(buf, "%02x%02x%02x%02x%02x\n", + pcf->resume_reason[0], + pcf->resume_reason[1], + pcf->resume_reason[2], + pcf->resume_reason[3], + pcf->resume_reason[4]); + + return n; +} +static DEVICE_ATTR(resume_reason, 0400, show_resume_reason, NULL); + +static struct attribute *pcf_sysfs_entries[] = { + &dev_attr_dump_regs.attr, + &dev_attr_resume_reason.attr, + NULL, +}; + +static struct attribute_group pcf_attr_group = { + .name = NULL, /* put in device directory */ + .attrs = pcf_sysfs_entries, +}; + +int pcf50633_register_irq(struct pcf50633 *pcf, int irq, + void (*handler) (int, void *), void *data) +{ + if (irq < 0 || irq > PCF50633_NUM_IRQ || !handler) + return -EINVAL; + + if (WARN_ON(pcf->irq_handler[irq].handler)) + return -EBUSY; + + mutex_lock(&pcf->lock); + pcf->irq_handler[irq].handler = handler; + pcf->irq_handler[irq].data = data; + mutex_unlock(&pcf->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(pcf50633_register_irq); + +int pcf50633_free_irq(struct pcf50633 *pcf, int irq) +{ + if (irq < 0 || irq > PCF50633_NUM_IRQ) + return -EINVAL; + + mutex_lock(&pcf->lock); + pcf->irq_handler[irq].handler = NULL; + mutex_unlock(&pcf->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(pcf50633_free_irq); + +static int __pcf50633_irq_mask_set(struct pcf50633 *pcf, int irq, u8 mask) +{ + u8 reg, bits, tmp; + int ret = 0, idx; + + idx = irq >> 3; + reg = PCF50633_REG_INT1M + idx; + bits = 1 << (irq & 0x07); + + mutex_lock(&pcf->lock); + + if (mask) { + ret = __pcf50633_read(pcf, reg, 1, &tmp); + if (ret < 0) + goto out; + + tmp |= bits; + + ret = __pcf50633_write(pcf, reg, 1, &tmp); + if (ret < 0) + goto out; + + pcf->mask_regs[idx] &= ~bits; + pcf->mask_regs[idx] |= bits; + } else { + ret = __pcf50633_read(pcf, reg, 1, &tmp); + if (ret < 0) + goto out; + + tmp &= ~bits; + + ret = __pcf50633_write(pcf, reg, 1, &tmp); + if (ret < 0) + goto out; + + pcf->mask_regs[idx] &= ~bits; + } +out: + mutex_unlock(&pcf->lock); + + return ret; +} + +int pcf50633_irq_mask(struct pcf50633 *pcf, int irq) +{ + dev_dbg(pcf->dev, "Masking IRQ %d\n", irq); + + return __pcf50633_irq_mask_set(pcf, irq, 1); +} +EXPORT_SYMBOL_GPL(pcf50633_irq_mask); + +int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq) +{ + dev_dbg(pcf->dev, "Unmasking IRQ %d\n", irq); + + return __pcf50633_irq_mask_set(pcf, irq, 0); +} +EXPORT_SYMBOL_GPL(pcf50633_irq_unmask); + +int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq) +{ + u8 reg, bits; + + reg = irq >> 3; + bits = 1 << (irq & 0x07); + + return pcf->mask_regs[reg] & bits; +} +EXPORT_SYMBOL_GPL(pcf50633_irq_mask_get); + +static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq) +{ + if (pcf->irq_handler[irq].handler) + pcf->irq_handler[irq].handler(irq, pcf->irq_handler[irq].data); +} + +/* Maximum amount of time ONKEY is held before emergency action is taken */ +#define PCF50633_ONKEY1S_TIMEOUT 8 + +static void pcf50633_irq_worker(struct work_struct *work) +{ + struct pcf50633 *pcf; + int ret, i, j; + u8 pcf_int[5], chgstat; + + pcf = container_of(work, struct pcf50633, irq_work); + + /* Read the 5 INT regs in one transaction */ + ret = pcf50633_read_block(pcf, PCF50633_REG_INT1, + ARRAY_SIZE(pcf_int), pcf_int); + if (ret != ARRAY_SIZE(pcf_int)) { + dev_err(pcf->dev, "Error reading INT registers\n"); + + /* + * If this doesn't ACK the interrupt to the chip, we'll be + * called once again as we're level triggered. + */ + goto out; + } + + /* defeat 8s death from lowsys on A5 */ + pcf50633_reg_write(pcf, PCF50633_REG_OOCSHDWN, 0x04); + + /* We immediately read the usb and adapter status. We thus make sure + * only of USBINS/USBREM IRQ handlers are called */ + if (pcf_int[0] & (PCF50633_INT1_USBINS | PCF50633_INT1_USBREM)) { + chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); + if (chgstat & (0x3 << 4)) + pcf_int[0] &= ~(1 << PCF50633_INT1_USBREM); + else + pcf_int[0] &= ~(1 << PCF50633_INT1_USBINS); + } + + /* Make sure only one of ADPINS or ADPREM is set */ + if (pcf_int[0] & (PCF50633_INT1_ADPINS | PCF50633_INT1_ADPREM)) { + chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); + if (chgstat & (0x3 << 4)) + pcf_int[0] &= ~(1 << PCF50633_INT1_ADPREM); + else + pcf_int[0] &= ~(1 << PCF50633_INT1_ADPINS); + } + + dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x " + "INT4=0x%02x INT5=0x%02x\n", pcf_int[0], + pcf_int[1], pcf_int[2], pcf_int[3], pcf_int[4]); + + /* Some revisions of the chip don't have a 8s standby mode on + * ONKEY1S press. We try to manually do it in such cases. */ + if ((pcf_int[0] & PCF50633_INT1_SECOND) && pcf->onkey1s_held) { + dev_info(pcf->dev, "ONKEY1S held for %d secs\n", + pcf->onkey1s_held); + if (pcf->onkey1s_held++ == PCF50633_ONKEY1S_TIMEOUT) + if (pcf->pdata->force_shutdown) + pcf->pdata->force_shutdown(pcf); + } + + if (pcf_int[2] & PCF50633_INT3_ONKEY1S) { + dev_info(pcf->dev, "ONKEY1S held\n"); + pcf->onkey1s_held = 1 ; + + /* Unmask IRQ_SECOND */ + pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT1M, + PCF50633_INT1_SECOND); + + /* Unmask IRQ_ONKEYR */ + pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT2M, + PCF50633_INT2_ONKEYR); + } + + if ((pcf_int[1] & PCF50633_INT2_ONKEYR) && pcf->onkey1s_held) { + pcf->onkey1s_held = 0; + + /* Mask SECOND and ONKEYR interrupts */ + if (pcf->mask_regs[0] & PCF50633_INT1_SECOND) + pcf50633_reg_set_bit_mask(pcf, + PCF50633_REG_INT1M, + PCF50633_INT1_SECOND, + PCF50633_INT1_SECOND); + + if (pcf->mask_regs[1] & PCF50633_INT2_ONKEYR) + pcf50633_reg_set_bit_mask(pcf, + PCF50633_REG_INT2M, + PCF50633_INT2_ONKEYR, + PCF50633_INT2_ONKEYR); + } + + /* Have we just resumed ? */ + if (pcf->is_suspended) { + pcf->is_suspended = 0; + + /* Set the resume reason filtering out non resumers */ + for (i = 0; i < ARRAY_SIZE(pcf_int); i++) + pcf->resume_reason[i] = pcf_int[i] & + pcf->pdata->resumers[i]; + + /* Make sure we don't pass on any ONKEY events to + * userspace now */ + pcf_int[1] &= ~(PCF50633_INT2_ONKEYR | PCF50633_INT2_ONKEYF); + } + + for (i = 0; i < ARRAY_SIZE(pcf_int); i++) { + /* Unset masked interrupts */ + pcf_int[i] &= ~pcf->mask_regs[i]; + + for (j = 0; j < 8 ; j++) + if (pcf_int[i] & (1 << j)) + pcf50633_irq_call_handler(pcf, (i * 8) + j); + } + +out: + put_device(pcf->dev); + enable_irq(pcf->irq); +} + +static irqreturn_t pcf50633_irq(int irq, void *data) +{ + struct pcf50633 *pcf = data; + + dev_dbg(pcf->dev, "pcf50633_irq\n"); + + get_device(pcf->dev); + disable_irq_nosync(pcf->irq); + queue_work(pcf->work_queue, &pcf->irq_work); + + return IRQ_HANDLED; +} + +static void +pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, + struct platform_device **pdev) +{ + int ret; + + *pdev = platform_device_alloc(name, -1); + if (!*pdev) { + dev_err(pcf->dev, "Falied to allocate %s\n", name); + return; + } + + (*pdev)->dev.parent = pcf->dev; + + ret = platform_device_add(*pdev); + if (ret) { + dev_err(pcf->dev, "Failed to register %s: %d\n", name, ret); + platform_device_put(*pdev); + *pdev = NULL; + } +} + +#ifdef CONFIG_PM +static int pcf50633_suspend(struct i2c_client *client, pm_message_t state) +{ + struct pcf50633 *pcf; + int ret = 0, i; + u8 res[5]; + + pcf = i2c_get_clientdata(client); + + /* Make sure our interrupt handlers are not called + * henceforth */ + disable_irq(pcf->irq); + + /* Make sure that any running IRQ worker has quit */ + cancel_work_sync(&pcf->irq_work); + + /* Save the masks */ + ret = pcf50633_read_block(pcf, PCF50633_REG_INT1M, + ARRAY_SIZE(pcf->suspend_irq_masks), + pcf->suspend_irq_masks); + if (ret < 0) { + dev_err(pcf->dev, "error saving irq masks\n"); + goto out; + } + + /* Write wakeup irq masks */ + for (i = 0; i < ARRAY_SIZE(res); i++) + res[i] = ~pcf->pdata->resumers[i]; + + ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, + ARRAY_SIZE(res), &res[0]); + if (ret < 0) { + dev_err(pcf->dev, "error writing wakeup irq masks\n"); + goto out; + } + + pcf->is_suspended = 1; + +out: + return ret; +} + +static int pcf50633_resume(struct i2c_client *client) +{ + struct pcf50633 *pcf; + int ret; + + pcf = i2c_get_clientdata(client); + + /* Write the saved mask registers */ + ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, + ARRAY_SIZE(pcf->suspend_irq_masks), + pcf->suspend_irq_masks); + if (ret < 0) + dev_err(pcf->dev, "Error restoring saved suspend masks\n"); + + /* Restore regulators' state */ + + + get_device(pcf->dev); + + /* + * Clear any pending interrupts and set resume reason if any. + * This will leave with enable_irq() + */ + pcf50633_irq_worker(&pcf->irq_work); + + return 0; +} +#else +#define pcf50633_suspend NULL +#define pcf50633_resume NULL +#endif + +static int __devinit pcf50633_probe(struct i2c_client *client, + const struct i2c_device_id *ids) +{ + struct pcf50633 *pcf; + struct pcf50633_platform_data *pdata = client->dev.platform_data; + int i, ret; + int version, variant; + + if (!client->irq) { + dev_err(&client->dev, "Missing IRQ\n"); + return -ENOENT; + } + + pcf = kzalloc(sizeof(*pcf), GFP_KERNEL); + if (!pcf) + return -ENOMEM; + + pcf->pdata = pdata; + + mutex_init(&pcf->lock); + + i2c_set_clientdata(client, pcf); + pcf->dev = &client->dev; + pcf->i2c_client = client; + pcf->irq = client->irq; + pcf->work_queue = create_singlethread_workqueue("pcf50633"); + + if (!pcf->work_queue) { + dev_err(&client->dev, "Failed to alloc workqueue\n"); + ret = -ENOMEM; + goto err_free; + } + + INIT_WORK(&pcf->irq_work, pcf50633_irq_worker); + + version = pcf50633_reg_read(pcf, 0); + variant = pcf50633_reg_read(pcf, 1); + if (version < 0 || variant < 0) { + dev_err(pcf->dev, "Unable to probe pcf50633\n"); + ret = -ENODEV; + goto err_destroy_workqueue; + } + + dev_info(pcf->dev, "Probed device version %d variant %d\n", + version, variant); + + /* Enable all interrupts except RTC SECOND */ + pcf->mask_regs[0] = 0x80; + pcf50633_reg_write(pcf, PCF50633_REG_INT1M, pcf->mask_regs[0]); + pcf50633_reg_write(pcf, PCF50633_REG_INT2M, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_INT3M, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00); + + ret = request_irq(client->irq, pcf50633_irq, + IRQF_TRIGGER_LOW, "pcf50633", pcf); + + if (ret) { + dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); + goto err_destroy_workqueue; + } + + /* Create sub devices */ + pcf50633_client_dev_register(pcf, "pcf50633-input", + &pcf->input_pdev); + pcf50633_client_dev_register(pcf, "pcf50633-rtc", + &pcf->rtc_pdev); + pcf50633_client_dev_register(pcf, "pcf50633-mbc", + &pcf->mbc_pdev); + pcf50633_client_dev_register(pcf, "pcf50633-adc", + &pcf->adc_pdev); + + for (i = 0; i < PCF50633_NUM_REGULATORS; i++) { + struct platform_device *pdev; + + pdev = platform_device_alloc("pcf50633-regltr", i); + if (!pdev) { + dev_err(pcf->dev, "Cannot create regulator %d\n", i); + continue; + } + + pdev->dev.parent = pcf->dev; + platform_device_add_data(pdev, &pdata->reg_init_data[i], + sizeof(pdata->reg_init_data[i])); + pcf->regulator_pdev[i] = pdev; + + platform_device_add(pdev); + } + + if (enable_irq_wake(client->irq) < 0) + dev_err(pcf->dev, "IRQ %u cannot be enabled as wake-up source" + "in this hardware revision", client->irq); + + ret = sysfs_create_group(&client->dev.kobj, &pcf_attr_group); + if (ret) + dev_err(pcf->dev, "error creating sysfs entries\n"); + + if (pdata->probe_done) + pdata->probe_done(pcf); + + return 0; + +err_destroy_workqueue: + destroy_workqueue(pcf->work_queue); +err_free: + i2c_set_clientdata(client, NULL); + kfree(pcf); + + return ret; +} + +static int __devexit pcf50633_remove(struct i2c_client *client) +{ + struct pcf50633 *pcf = i2c_get_clientdata(client); + int i; + + free_irq(pcf->irq, pcf); + destroy_workqueue(pcf->work_queue); + + platform_device_unregister(pcf->input_pdev); + platform_device_unregister(pcf->rtc_pdev); + platform_device_unregister(pcf->mbc_pdev); + platform_device_unregister(pcf->adc_pdev); + + for (i = 0; i < PCF50633_NUM_REGULATORS; i++) + platform_device_unregister(pcf->regulator_pdev[i]); + + kfree(pcf); + + return 0; +} + +static struct i2c_device_id pcf50633_id_table[] = { + {"pcf50633", 0x73}, + {/* end of list */} +}; + +static struct i2c_driver pcf50633_driver = { + .driver = { + .name = "pcf50633", + }, + .id_table = pcf50633_id_table, + .probe = pcf50633_probe, + .remove = __devexit_p(pcf50633_remove), + .suspend = pcf50633_suspend, + .resume = pcf50633_resume, +}; + +static int __init pcf50633_init(void) +{ + return i2c_add_driver(&pcf50633_driver); +} + +static void __exit pcf50633_exit(void) +{ + i2c_del_driver(&pcf50633_driver); +} + +MODULE_DESCRIPTION("I2C chip driver for NXP PCF50633 PMU"); +MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>"); +MODULE_LICENSE("GPL"); + +subsys_initcall(pcf50633_init); +module_exit(pcf50633_exit); diff --git a/drivers/mfd/pcf50633-gpio.c b/drivers/mfd/pcf50633-gpio.c new file mode 100644 index 00000000000..9ab19a8f669 --- /dev/null +++ b/drivers/mfd/pcf50633-gpio.c @@ -0,0 +1,121 @@ +/* NXP PCF50633 GPIO Driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Balaji Rao <balajirrao@openmoko.org> + * All rights reserved. + * + * Broken down from monstrous PCF50633 driver mainly by + * Harald Welte, Andy Green and Werner Almesberger + * + * 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 <linux/kernel.h> +#include <linux/module.h> + +#include <linux/mfd/pcf50633/core.h> +#include <linux/mfd/pcf50633/gpio.h> + +enum pcf50633_regulator_id { + PCF50633_REGULATOR_AUTO, + PCF50633_REGULATOR_DOWN1, + PCF50633_REGULATOR_DOWN2, + PCF50633_REGULATOR_LDO1, + PCF50633_REGULATOR_LDO2, + PCF50633_REGULATOR_LDO3, + PCF50633_REGULATOR_LDO4, + PCF50633_REGULATOR_LDO5, + PCF50633_REGULATOR_LDO6, + PCF50633_REGULATOR_HCLDO, + PCF50633_REGULATOR_MEMLDO, +}; + +#define PCF50633_REG_AUTOOUT 0x1a +#define PCF50633_REG_DOWN1OUT 0x1e +#define PCF50633_REG_DOWN2OUT 0x22 +#define PCF50633_REG_MEMLDOOUT 0x26 +#define PCF50633_REG_LDO1OUT 0x2d +#define PCF50633_REG_LDO2OUT 0x2f +#define PCF50633_REG_LDO3OUT 0x31 +#define PCF50633_REG_LDO4OUT 0x33 +#define PCF50633_REG_LDO5OUT 0x35 +#define PCF50633_REG_LDO6OUT 0x37 +#define PCF50633_REG_HCLDOOUT 0x39 + +static const u8 pcf50633_regulator_registers[PCF50633_NUM_REGULATORS] = { + [PCF50633_REGULATOR_AUTO] = PCF50633_REG_AUTOOUT, + [PCF50633_REGULATOR_DOWN1] = PCF50633_REG_DOWN1OUT, + [PCF50633_REGULATOR_DOWN2] = PCF50633_REG_DOWN2OUT, + [PCF50633_REGULATOR_MEMLDO] = PCF50633_REG_MEMLDOOUT, + [PCF50633_REGULATOR_LDO1] = PCF50633_REG_LDO1OUT, + [PCF50633_REGULATOR_LDO2] = PCF50633_REG_LDO2OUT, + [PCF50633_REGULATOR_LDO3] = PCF50633_REG_LDO3OUT, + [PCF50633_REGULATOR_LDO4] = PCF50633_REG_LDO4OUT, + [PCF50633_REGULATOR_LDO5] = PCF50633_REG_LDO5OUT, + [PCF50633_REGULATOR_LDO6] = PCF50633_REG_LDO6OUT, + [PCF50633_REGULATOR_HCLDO] = PCF50633_REG_HCLDOOUT, +}; + +int pcf50633_gpio_set(struct pcf50633 *pcf, int gpio, u8 val) +{ + u8 reg; + + reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG; + + return pcf50633_reg_set_bit_mask(pcf, reg, 0x07, val); +} +EXPORT_SYMBOL_GPL(pcf50633_gpio_set); + +u8 pcf50633_gpio_get(struct pcf50633 *pcf, int gpio) +{ + u8 reg, val; + + reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG; + val = pcf50633_reg_read(pcf, reg) & 0x07; + + return val; +} +EXPORT_SYMBOL_GPL(pcf50633_gpio_get); + +int pcf50633_gpio_invert_set(struct pcf50633 *pcf, int gpio, int invert) +{ + u8 val, reg; + + reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG; + val = !!invert << 3; + + return pcf50633_reg_set_bit_mask(pcf, reg, 1 << 3, val); +} +EXPORT_SYMBOL_GPL(pcf50633_gpio_invert_set); + +int pcf50633_gpio_invert_get(struct pcf50633 *pcf, int gpio) +{ + u8 reg, val; + + reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG; + val = pcf50633_reg_read(pcf, reg); + + return val & (1 << 3); +} +EXPORT_SYMBOL_GPL(pcf50633_gpio_invert_get); + +int pcf50633_gpio_power_supply_set(struct pcf50633 *pcf, + int gpio, int regulator, int on) +{ + u8 reg, val, mask; + + /* the *ENA register is always one after the *OUT register */ + reg = pcf50633_regulator_registers[regulator] + 1; + + val = !!on << (gpio - PCF50633_GPIO1); + mask = 1 << (gpio - PCF50633_GPIO1); + + return pcf50633_reg_set_bit_mask(pcf, reg, mask, val); +} +EXPORT_SYMBOL_GPL(pcf50633_gpio_power_supply_set); + +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/sh_mobile_sdhi.c b/drivers/mfd/sh_mobile_sdhi.c new file mode 100644 index 00000000000..03efae8041a --- /dev/null +++ b/drivers/mfd/sh_mobile_sdhi.c @@ -0,0 +1,156 @@ +/* + * SuperH Mobile SDHI + * + * Copyright (C) 2009 Magnus Damm + * + * 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. + * + * Based on "Compaq ASIC3 support": + * + * Copyright 2001 Compaq Computer Corporation. + * Copyright 2004-2005 Phil Blundell + * Copyright 2007-2008 OpenedHand Ltd. + * + * Authors: Phil Blundell <pb@handhelds.org>, + * Samuel Ortiz <sameo@openedhand.com> + * + */ + +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/platform_device.h> + +#include <linux/mfd/core.h> +#include <linux/mfd/tmio.h> +#include <linux/mfd/sh_mobile_sdhi.h> + +struct sh_mobile_sdhi { + struct clk *clk; + struct tmio_mmc_data mmc_data; + struct mfd_cell cell_mmc; +}; + +static struct resource sh_mobile_sdhi_resources[] = { + { + .start = 0x000, + .end = 0x1ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell sh_mobile_sdhi_cell = { + .name = "tmio-mmc", + .num_resources = ARRAY_SIZE(sh_mobile_sdhi_resources), + .resources = sh_mobile_sdhi_resources, +}; + +static void sh_mobile_sdhi_set_pwr(struct platform_device *tmio, int state) +{ + struct platform_device *pdev = to_platform_device(tmio->dev.parent); + struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; + + if (p && p->set_pwr) + p->set_pwr(pdev, state); +} + +static int __init sh_mobile_sdhi_probe(struct platform_device *pdev) +{ + struct sh_mobile_sdhi *priv; + struct resource *mem; + char clk_name[8]; + int ret, irq; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) + dev_err(&pdev->dev, "missing MEM resource\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + dev_err(&pdev->dev, "missing IRQ resource\n"); + + if (!mem || (irq < 0)) + return -EINVAL; + + priv = kzalloc(sizeof(struct sh_mobile_sdhi), GFP_KERNEL); + if (priv == NULL) { + dev_err(&pdev->dev, "kzalloc failed\n"); + return -ENOMEM; + } + + snprintf(clk_name, sizeof(clk_name), "sdhi%d", pdev->id); + priv->clk = clk_get(&pdev->dev, clk_name); + if (IS_ERR(priv->clk)) { + dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name); + ret = PTR_ERR(priv->clk); + kfree(priv); + return ret; + } + + clk_enable(priv->clk); + + /* FIXME: silly const unsigned int hclk */ + *(unsigned int *)&priv->mmc_data.hclk = clk_get_rate(priv->clk); + priv->mmc_data.set_pwr = sh_mobile_sdhi_set_pwr; + + memcpy(&priv->cell_mmc, &sh_mobile_sdhi_cell, sizeof(priv->cell_mmc)); + priv->cell_mmc.driver_data = &priv->mmc_data; + priv->cell_mmc.platform_data = &priv->cell_mmc; + priv->cell_mmc.data_size = sizeof(priv->cell_mmc); + + platform_set_drvdata(pdev, priv); + + ret = mfd_add_devices(&pdev->dev, pdev->id, + &priv->cell_mmc, 1, mem, irq); + if (ret) { + clk_disable(priv->clk); + clk_put(priv->clk); + kfree(priv); + } + + return ret; +} + +static int sh_mobile_sdhi_remove(struct platform_device *pdev) +{ + struct sh_mobile_sdhi *priv = platform_get_drvdata(pdev); + + mfd_remove_devices(&pdev->dev); + clk_disable(priv->clk); + clk_put(priv->clk); + kfree(priv); + + return 0; +} + +static struct platform_driver sh_mobile_sdhi_driver = { + .driver = { + .name = "sh_mobile_sdhi", + .owner = THIS_MODULE, + }, + .probe = sh_mobile_sdhi_probe, + .remove = __devexit_p(sh_mobile_sdhi_remove), +}; + +static int __init sh_mobile_sdhi_init(void) +{ + return platform_driver_register(&sh_mobile_sdhi_driver); +} + +static void __exit sh_mobile_sdhi_exit(void) +{ + platform_driver_unregister(&sh_mobile_sdhi_driver); +} + +module_init(sh_mobile_sdhi_init); +module_exit(sh_mobile_sdhi_exit); + +MODULE_DESCRIPTION("SuperH Mobile SDHI driver"); +MODULE_AUTHOR("Magnus Damm"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c new file mode 100644 index 00000000000..0cc5eeff5ee --- /dev/null +++ b/drivers/mfd/sm501.c @@ -0,0 +1,1768 @@ +/* linux/drivers/mfd/sm501.c + * + * Copyright (C) 2006 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * Vincent Sanders <vince@simtec.co.uk> + * + * 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. + * + * SM501 MFD driver +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/pci.h> +#include <linux/i2c-gpio.h> + +#include <linux/sm501.h> +#include <linux/sm501-regs.h> +#include <linux/serial_8250.h> + +#include <asm/io.h> + +struct sm501_device { + struct list_head list; + struct platform_device pdev; +}; + +struct sm501_gpio; + +#ifdef CONFIG_MFD_SM501_GPIO +#include <linux/gpio.h> + +struct sm501_gpio_chip { + struct gpio_chip gpio; + struct sm501_gpio *ourgpio; /* to get back to parent. */ + void __iomem *regbase; + void __iomem *control; /* address of control reg. */ +}; + +struct sm501_gpio { + struct sm501_gpio_chip low; + struct sm501_gpio_chip high; + spinlock_t lock; + + unsigned int registered : 1; + void __iomem *regs; + struct resource *regs_res; +}; +#else +struct sm501_gpio { + /* no gpio support, empty definition for sm501_devdata. */ +}; +#endif + +struct sm501_devdata { + spinlock_t reg_lock; + struct mutex clock_lock; + struct list_head devices; + struct sm501_gpio gpio; + + struct device *dev; + struct resource *io_res; + struct resource *mem_res; + struct resource *regs_claim; + struct sm501_platdata *platdata; + + + unsigned int in_suspend; + unsigned long pm_misc; + + int unit_power[20]; + unsigned int pdev_id; + unsigned int irq; + void __iomem *regs; + unsigned int rev; +}; + + +#define MHZ (1000 * 1000) + +#ifdef DEBUG +static const unsigned int div_tab[] = { + [0] = 1, + [1] = 2, + [2] = 4, + [3] = 8, + [4] = 16, + [5] = 32, + [6] = 64, + [7] = 128, + [8] = 3, + [9] = 6, + [10] = 12, + [11] = 24, + [12] = 48, + [13] = 96, + [14] = 192, + [15] = 384, + [16] = 5, + [17] = 10, + [18] = 20, + [19] = 40, + [20] = 80, + [21] = 160, + [22] = 320, + [23] = 604, +}; + +static unsigned long decode_div(unsigned long pll2, unsigned long val, + unsigned int lshft, unsigned int selbit, + unsigned long mask) +{ + if (val & selbit) + pll2 = 288 * MHZ; + + return pll2 / div_tab[(val >> lshft) & mask]; +} + +#define fmt_freq(x) ((x) / MHZ), ((x) % MHZ), (x) + +/* sm501_dump_clk + * + * Print out the current clock configuration for the device +*/ + +static void sm501_dump_clk(struct sm501_devdata *sm) +{ + unsigned long misct = readl(sm->regs + SM501_MISC_TIMING); + unsigned long pm0 = readl(sm->regs + SM501_POWER_MODE_0_CLOCK); + unsigned long pm1 = readl(sm->regs + SM501_POWER_MODE_1_CLOCK); + unsigned long pmc = readl(sm->regs + SM501_POWER_MODE_CONTROL); + unsigned long sdclk0, sdclk1; + unsigned long pll2 = 0; + + switch (misct & 0x30) { + case 0x00: + pll2 = 336 * MHZ; + break; + case 0x10: + pll2 = 288 * MHZ; + break; + case 0x20: + pll2 = 240 * MHZ; + break; + case 0x30: + pll2 = 192 * MHZ; + break; + } + + sdclk0 = (misct & (1<<12)) ? pll2 : 288 * MHZ; + sdclk0 /= div_tab[((misct >> 8) & 0xf)]; + + sdclk1 = (misct & (1<<20)) ? pll2 : 288 * MHZ; + sdclk1 /= div_tab[((misct >> 16) & 0xf)]; + + dev_dbg(sm->dev, "MISCT=%08lx, PM0=%08lx, PM1=%08lx\n", + misct, pm0, pm1); + + dev_dbg(sm->dev, "PLL2 = %ld.%ld MHz (%ld), SDCLK0=%08lx, SDCLK1=%08lx\n", + fmt_freq(pll2), sdclk0, sdclk1); + + dev_dbg(sm->dev, "SDRAM: PM0=%ld, PM1=%ld\n", sdclk0, sdclk1); + + dev_dbg(sm->dev, "PM0[%c]: " + "P2 %ld.%ld MHz (%ld), V2 %ld.%ld (%ld), " + "M %ld.%ld (%ld), MX1 %ld.%ld (%ld)\n", + (pmc & 3 ) == 0 ? '*' : '-', + fmt_freq(decode_div(pll2, pm0, 24, 1<<29, 31)), + fmt_freq(decode_div(pll2, pm0, 16, 1<<20, 15)), + fmt_freq(decode_div(pll2, pm0, 8, 1<<12, 15)), + fmt_freq(decode_div(pll2, pm0, 0, 1<<4, 15))); + + dev_dbg(sm->dev, "PM1[%c]: " + "P2 %ld.%ld MHz (%ld), V2 %ld.%ld (%ld), " + "M %ld.%ld (%ld), MX1 %ld.%ld (%ld)\n", + (pmc & 3 ) == 1 ? '*' : '-', + fmt_freq(decode_div(pll2, pm1, 24, 1<<29, 31)), + fmt_freq(decode_div(pll2, pm1, 16, 1<<20, 15)), + fmt_freq(decode_div(pll2, pm1, 8, 1<<12, 15)), + fmt_freq(decode_div(pll2, pm1, 0, 1<<4, 15))); +} + +static void sm501_dump_regs(struct sm501_devdata *sm) +{ + void __iomem *regs = sm->regs; + + dev_info(sm->dev, "System Control %08x\n", + readl(regs + SM501_SYSTEM_CONTROL)); + dev_info(sm->dev, "Misc Control %08x\n", + readl(regs + SM501_MISC_CONTROL)); + dev_info(sm->dev, "GPIO Control Low %08x\n", + readl(regs + SM501_GPIO31_0_CONTROL)); + dev_info(sm->dev, "GPIO Control Hi %08x\n", + readl(regs + SM501_GPIO63_32_CONTROL)); + dev_info(sm->dev, "DRAM Control %08x\n", + readl(regs + SM501_DRAM_CONTROL)); + dev_info(sm->dev, "Arbitration Ctrl %08x\n", + readl(regs + SM501_ARBTRTN_CONTROL)); + dev_info(sm->dev, "Misc Timing %08x\n", + readl(regs + SM501_MISC_TIMING)); +} + +static void sm501_dump_gate(struct sm501_devdata *sm) +{ + dev_info(sm->dev, "CurrentGate %08x\n", + readl(sm->regs + SM501_CURRENT_GATE)); + dev_info(sm->dev, "CurrentClock %08x\n", + readl(sm->regs + SM501_CURRENT_CLOCK)); + dev_info(sm->dev, "PowerModeControl %08x\n", + readl(sm->regs + SM501_POWER_MODE_CONTROL)); +} + +#else +static inline void sm501_dump_gate(struct sm501_devdata *sm) { } +static inline void sm501_dump_regs(struct sm501_devdata *sm) { } +static inline void sm501_dump_clk(struct sm501_devdata *sm) { } +#endif + +/* sm501_sync_regs + * + * ensure the +*/ + +static void sm501_sync_regs(struct sm501_devdata *sm) +{ + readl(sm->regs); +} + +static inline void sm501_mdelay(struct sm501_devdata *sm, unsigned int delay) +{ + /* during suspend/resume, we are currently not allowed to sleep, + * so change to using mdelay() instead of msleep() if we + * are in one of these paths */ + + if (sm->in_suspend) + mdelay(delay); + else + msleep(delay); +} + +/* sm501_misc_control + * + * alters the miscellaneous control parameters +*/ + +int sm501_misc_control(struct device *dev, + unsigned long set, unsigned long clear) +{ + struct sm501_devdata *sm = dev_get_drvdata(dev); + unsigned long misc; + unsigned long save; + unsigned long to; + + spin_lock_irqsave(&sm->reg_lock, save); + + misc = readl(sm->regs + SM501_MISC_CONTROL); + to = (misc & ~clear) | set; + + if (to != misc) { + writel(to, sm->regs + SM501_MISC_CONTROL); + sm501_sync_regs(sm); + + dev_dbg(sm->dev, "MISC_CONTROL %08lx\n", misc); + } + + spin_unlock_irqrestore(&sm->reg_lock, save); + return to; +} + +EXPORT_SYMBOL_GPL(sm501_misc_control); + +/* sm501_modify_reg + * + * Modify a register in the SM501 which may be shared with other + * drivers. +*/ + +unsigned long sm501_modify_reg(struct device *dev, + unsigned long reg, + unsigned long set, + unsigned long clear) +{ + struct sm501_devdata *sm = dev_get_drvdata(dev); + unsigned long data; + unsigned long save; + + spin_lock_irqsave(&sm->reg_lock, save); + + data = readl(sm->regs + reg); + data |= set; + data &= ~clear; + + writel(data, sm->regs + reg); + sm501_sync_regs(sm); + + spin_unlock_irqrestore(&sm->reg_lock, save); + + return data; +} + +EXPORT_SYMBOL_GPL(sm501_modify_reg); + +/* sm501_unit_power + * + * alters the power active gate to set specific units on or off + */ + +int sm501_unit_power(struct device *dev, unsigned int unit, unsigned int to) +{ + struct sm501_devdata *sm = dev_get_drvdata(dev); + unsigned long mode; + unsigned long gate; + unsigned long clock; + + mutex_lock(&sm->clock_lock); + + mode = readl(sm->regs + SM501_POWER_MODE_CONTROL); + gate = readl(sm->regs + SM501_CURRENT_GATE); + clock = readl(sm->regs + SM501_CURRENT_CLOCK); + + mode &= 3; /* get current power mode */ + + if (unit >= ARRAY_SIZE(sm->unit_power)) { + dev_err(dev, "%s: bad unit %d\n", __func__, unit); + goto already; + } + + dev_dbg(sm->dev, "%s: unit %d, cur %d, to %d\n", __func__, unit, + sm->unit_power[unit], to); + + if (to == 0 && sm->unit_power[unit] == 0) { + dev_err(sm->dev, "unit %d is already shutdown\n", unit); + goto already; + } + + sm->unit_power[unit] += to ? 1 : -1; + to = sm->unit_power[unit] ? 1 : 0; + + if (to) { + if (gate & (1 << unit)) + goto already; + gate |= (1 << unit); + } else { + if (!(gate & (1 << unit))) + goto already; + gate &= ~(1 << unit); + } + + switch (mode) { + case 1: + writel(gate, sm->regs + SM501_POWER_MODE_0_GATE); + writel(clock, sm->regs + SM501_POWER_MODE_0_CLOCK); + mode = 0; + break; + case 2: + case 0: + writel(gate, sm->regs + SM501_POWER_MODE_1_GATE); + writel(clock, sm->regs + SM501_POWER_MODE_1_CLOCK); + mode = 1; + break; + + default: + gate = -1; + goto already; + } + + writel(mode, sm->regs + SM501_POWER_MODE_CONTROL); + sm501_sync_regs(sm); + + dev_dbg(sm->dev, "gate %08lx, clock %08lx, mode %08lx\n", + gate, clock, mode); + + sm501_mdelay(sm, 16); + + already: + mutex_unlock(&sm->clock_lock); + return gate; +} + +EXPORT_SYMBOL_GPL(sm501_unit_power); + + +/* Perform a rounded division. */ +static long sm501fb_round_div(long num, long denom) +{ + /* n / d + 1 / 2 = (2n + d) / 2d */ + return (2 * num + denom) / (2 * denom); +} + +/* clock value structure. */ +struct sm501_clock { + unsigned long mclk; + int divider; + int shift; + unsigned int m, n, k; +}; + +/* sm501_calc_clock + * + * Calculates the nearest discrete clock frequency that + * can be achieved with the specified input clock. + * the maximum divisor is 3 or 5 + */ + +static int sm501_calc_clock(unsigned long freq, + struct sm501_clock *clock, + int max_div, + unsigned long mclk, + long *best_diff) +{ + int ret = 0; + int divider; + int shift; + long diff; + + /* try dividers 1 and 3 for CRT and for panel, + try divider 5 for panel only.*/ + + for (divider = 1; divider <= max_div; divider += 2) { + /* try all 8 shift values.*/ + for (shift = 0; shift < 8; shift++) { + /* Calculate difference to requested clock */ + diff = sm501fb_round_div(mclk, divider << shift) - freq; + if (diff < 0) + diff = -diff; + + /* If it is less than the current, use it */ + if (diff < *best_diff) { + *best_diff = diff; + + clock->mclk = mclk; + clock->divider = divider; + clock->shift = shift; + ret = 1; + } + } + } + + return ret; +} + +/* sm501_calc_pll + * + * Calculates the nearest discrete clock frequency that can be + * achieved using the programmable PLL. + * the maximum divisor is 3 or 5 + */ + +static unsigned long sm501_calc_pll(unsigned long freq, + struct sm501_clock *clock, + int max_div) +{ + unsigned long mclk; + unsigned int m, n, k; + long best_diff = 999999999; + + /* + * The SM502 datasheet doesn't specify the min/max values for M and N. + * N = 1 at least doesn't work in practice. + */ + for (m = 2; m <= 255; m++) { + for (n = 2; n <= 127; n++) { + for (k = 0; k <= 1; k++) { + mclk = (24000000UL * m / n) >> k; + + if (sm501_calc_clock(freq, clock, max_div, + mclk, &best_diff)) { + clock->m = m; + clock->n = n; + clock->k = k; + } + } + } + } + + /* Return best clock. */ + return clock->mclk / (clock->divider << clock->shift); +} + +/* sm501_select_clock + * + * Calculates the nearest discrete clock frequency that can be + * achieved using the 288MHz and 336MHz PLLs. + * the maximum divisor is 3 or 5 + */ + +static unsigned long sm501_select_clock(unsigned long freq, + struct sm501_clock *clock, + int max_div) +{ + unsigned long mclk; + long best_diff = 999999999; + + /* Try 288MHz and 336MHz clocks. */ + for (mclk = 288000000; mclk <= 336000000; mclk += 48000000) { + sm501_calc_clock(freq, clock, max_div, mclk, &best_diff); + } + + /* Return best clock. */ + return clock->mclk / (clock->divider << clock->shift); +} + +/* sm501_set_clock + * + * set one of the four clock sources to the closest available frequency to + * the one specified +*/ + +unsigned long sm501_set_clock(struct device *dev, + int clksrc, + unsigned long req_freq) +{ + struct sm501_devdata *sm = dev_get_drvdata(dev); + unsigned long mode = readl(sm->regs + SM501_POWER_MODE_CONTROL); + unsigned long gate = readl(sm->regs + SM501_CURRENT_GATE); + unsigned long clock = readl(sm->regs + SM501_CURRENT_CLOCK); + unsigned char reg; + unsigned int pll_reg = 0; + unsigned long sm501_freq; /* the actual frequency acheived */ + + struct sm501_clock to; + + /* find achivable discrete frequency and setup register value + * accordingly, V2XCLK, MCLK and M1XCLK are the same P2XCLK + * has an extra bit for the divider */ + + switch (clksrc) { + case SM501_CLOCK_P2XCLK: + /* This clock is divided in half so to achive the + * requested frequency the value must be multiplied by + * 2. This clock also has an additional pre divisor */ + + if (sm->rev >= 0xC0) { + /* SM502 -> use the programmable PLL */ + sm501_freq = (sm501_calc_pll(2 * req_freq, + &to, 5) / 2); + reg = to.shift & 0x07;/* bottom 3 bits are shift */ + if (to.divider == 3) + reg |= 0x08; /* /3 divider required */ + else if (to.divider == 5) + reg |= 0x10; /* /5 divider required */ + reg |= 0x40; /* select the programmable PLL */ + pll_reg = 0x20000 | (to.k << 15) | (to.n << 8) | to.m; + } else { + sm501_freq = (sm501_select_clock(2 * req_freq, + &to, 5) / 2); + reg = to.shift & 0x07;/* bottom 3 bits are shift */ + if (to.divider == 3) + reg |= 0x08; /* /3 divider required */ + else if (to.divider == 5) + reg |= 0x10; /* /5 divider required */ + if (to.mclk != 288000000) + reg |= 0x20; /* which mclk pll is source */ + } + break; + + case SM501_CLOCK_V2XCLK: + /* This clock is divided in half so to achive the + * requested frequency the value must be multiplied by 2. */ + + sm501_freq = (sm501_select_clock(2 * req_freq, &to, 3) / 2); + reg=to.shift & 0x07; /* bottom 3 bits are shift */ + if (to.divider == 3) + reg |= 0x08; /* /3 divider required */ + if (to.mclk != 288000000) + reg |= 0x10; /* which mclk pll is source */ + break; + + case SM501_CLOCK_MCLK: + case SM501_CLOCK_M1XCLK: + /* These clocks are the same and not further divided */ + + sm501_freq = sm501_select_clock( req_freq, &to, 3); + reg=to.shift & 0x07; /* bottom 3 bits are shift */ + if (to.divider == 3) + reg |= 0x08; /* /3 divider required */ + if (to.mclk != 288000000) + reg |= 0x10; /* which mclk pll is source */ + break; + + default: + return 0; /* this is bad */ + } + + mutex_lock(&sm->clock_lock); + + mode = readl(sm->regs + SM501_POWER_MODE_CONTROL); + gate = readl(sm->regs + SM501_CURRENT_GATE); + clock = readl(sm->regs + SM501_CURRENT_CLOCK); + + clock = clock & ~(0xFF << clksrc); + clock |= reg<<clksrc; + + mode &= 3; /* find current mode */ + + switch (mode) { + case 1: + writel(gate, sm->regs + SM501_POWER_MODE_0_GATE); + writel(clock, sm->regs + SM501_POWER_MODE_0_CLOCK); + mode = 0; + break; + case 2: + case 0: + writel(gate, sm->regs + SM501_POWER_MODE_1_GATE); + writel(clock, sm->regs + SM501_POWER_MODE_1_CLOCK); + mode = 1; + break; + + default: + mutex_unlock(&sm->clock_lock); + return -1; + } + + writel(mode, sm->regs + SM501_POWER_MODE_CONTROL); + + if (pll_reg) + writel(pll_reg, sm->regs + SM501_PROGRAMMABLE_PLL_CONTROL); + + sm501_sync_regs(sm); + + dev_dbg(sm->dev, "gate %08lx, clock %08lx, mode %08lx\n", + gate, clock, mode); + + sm501_mdelay(sm, 16); + mutex_unlock(&sm->clock_lock); + + sm501_dump_clk(sm); + + return sm501_freq; +} + +EXPORT_SYMBOL_GPL(sm501_set_clock); + +/* sm501_find_clock + * + * finds the closest available frequency for a given clock +*/ + +unsigned long sm501_find_clock(struct device *dev, + int clksrc, + unsigned long req_freq) +{ + struct sm501_devdata *sm = dev_get_drvdata(dev); + unsigned long sm501_freq; /* the frequency achiveable by the 501 */ + struct sm501_clock to; + + switch (clksrc) { + case SM501_CLOCK_P2XCLK: + if (sm->rev >= 0xC0) { + /* SM502 -> use the programmable PLL */ + sm501_freq = (sm501_calc_pll(2 * req_freq, + &to, 5) / 2); + } else { + sm501_freq = (sm501_select_clock(2 * req_freq, + &to, 5) / 2); + } + break; + + case SM501_CLOCK_V2XCLK: + sm501_freq = (sm501_select_clock(2 * req_freq, &to, 3) / 2); + break; + + case SM501_CLOCK_MCLK: + case SM501_CLOCK_M1XCLK: + sm501_freq = sm501_select_clock(req_freq, &to, 3); + break; + + default: + sm501_freq = 0; /* error */ + } + + return sm501_freq; +} + +EXPORT_SYMBOL_GPL(sm501_find_clock); + +static struct sm501_device *to_sm_device(struct platform_device *pdev) +{ + return container_of(pdev, struct sm501_device, pdev); +} + +/* sm501_device_release + * + * A release function for the platform devices we create to allow us to + * free any items we allocated +*/ + +static void sm501_device_release(struct device *dev) +{ + kfree(to_sm_device(to_platform_device(dev))); +} + +/* sm501_create_subdev + * + * Create a skeleton platform device with resources for passing to a + * sub-driver +*/ + +static struct platform_device * +sm501_create_subdev(struct sm501_devdata *sm, char *name, + unsigned int res_count, unsigned int platform_data_size) +{ + struct sm501_device *smdev; + + smdev = kzalloc(sizeof(struct sm501_device) + + (sizeof(struct resource) * res_count) + + platform_data_size, GFP_KERNEL); + if (!smdev) + return NULL; + + smdev->pdev.dev.release = sm501_device_release; + + smdev->pdev.name = name; + smdev->pdev.id = sm->pdev_id; + smdev->pdev.dev.parent = sm->dev; + + if (res_count) { + smdev->pdev.resource = (struct resource *)(smdev+1); + smdev->pdev.num_resources = res_count; + } + if (platform_data_size) + smdev->pdev.dev.platform_data = (void *)(smdev+1); + + return &smdev->pdev; +} + +/* sm501_register_device + * + * Register a platform device created with sm501_create_subdev() +*/ + +static int sm501_register_device(struct sm501_devdata *sm, + struct platform_device *pdev) +{ + struct sm501_device *smdev = to_sm_device(pdev); + int ptr; + int ret; + + for (ptr = 0; ptr < pdev->num_resources; ptr++) { + printk(KERN_DEBUG "%s[%d] flags %08lx: %08llx..%08llx\n", + pdev->name, ptr, + pdev->resource[ptr].flags, + (unsigned long long)pdev->resource[ptr].start, + (unsigned long long)pdev->resource[ptr].end); + } + + ret = platform_device_register(pdev); + + if (ret >= 0) { + dev_dbg(sm->dev, "registered %s\n", pdev->name); + list_add_tail(&smdev->list, &sm->devices); + } else + dev_err(sm->dev, "error registering %s (%d)\n", + pdev->name, ret); + + return ret; +} + +/* sm501_create_subio + * + * Fill in an IO resource for a sub device +*/ + +static void sm501_create_subio(struct sm501_devdata *sm, + struct resource *res, + resource_size_t offs, + resource_size_t size) +{ + res->flags = IORESOURCE_MEM; + res->parent = sm->io_res; + res->start = sm->io_res->start + offs; + res->end = res->start + size - 1; +} + +/* sm501_create_mem + * + * Fill in an MEM resource for a sub device +*/ + +static void sm501_create_mem(struct sm501_devdata *sm, + struct resource *res, + resource_size_t *offs, + resource_size_t size) +{ + *offs -= size; /* adjust memory size */ + + res->flags = IORESOURCE_MEM; + res->parent = sm->mem_res; + res->start = sm->mem_res->start + *offs; + res->end = res->start + size - 1; +} + +/* sm501_create_irq + * + * Fill in an IRQ resource for a sub device +*/ + +static void sm501_create_irq(struct sm501_devdata *sm, + struct resource *res) +{ + res->flags = IORESOURCE_IRQ; + res->parent = NULL; + res->start = res->end = sm->irq; +} + +static int sm501_register_usbhost(struct sm501_devdata *sm, + resource_size_t *mem_avail) +{ + struct platform_device *pdev; + + pdev = sm501_create_subdev(sm, "sm501-usb", 3, 0); + if (!pdev) + return -ENOMEM; + + sm501_create_subio(sm, &pdev->resource[0], 0x40000, 0x20000); + sm501_create_mem(sm, &pdev->resource[1], mem_avail, 256*1024); + sm501_create_irq(sm, &pdev->resource[2]); + + return sm501_register_device(sm, pdev); +} + +static void sm501_setup_uart_data(struct sm501_devdata *sm, + struct plat_serial8250_port *uart_data, + unsigned int offset) +{ + uart_data->membase = sm->regs + offset; + uart_data->mapbase = sm->io_res->start + offset; + uart_data->iotype = UPIO_MEM; + uart_data->irq = sm->irq; + uart_data->flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST | UPF_SHARE_IRQ; + uart_data->regshift = 2; + uart_data->uartclk = (9600 * 16); +} + +static int sm501_register_uart(struct sm501_devdata *sm, int devices) +{ + struct platform_device *pdev; + struct plat_serial8250_port *uart_data; + + pdev = sm501_create_subdev(sm, "serial8250", 0, + sizeof(struct plat_serial8250_port) * 3); + if (!pdev) + return -ENOMEM; + + uart_data = pdev->dev.platform_data; + + if (devices & SM501_USE_UART0) { + sm501_setup_uart_data(sm, uart_data++, 0x30000); + sm501_unit_power(sm->dev, SM501_GATE_UART0, 1); + sm501_modify_reg(sm->dev, SM501_IRQ_MASK, 1 << 12, 0); + sm501_modify_reg(sm->dev, SM501_GPIO63_32_CONTROL, 0x01e0, 0); + } + if (devices & SM501_USE_UART1) { + sm501_setup_uart_data(sm, uart_data++, 0x30020); + sm501_unit_power(sm->dev, SM501_GATE_UART1, 1); + sm501_modify_reg(sm->dev, SM501_IRQ_MASK, 1 << 13, 0); + sm501_modify_reg(sm->dev, SM501_GPIO63_32_CONTROL, 0x1e00, 0); + } + + pdev->id = PLAT8250_DEV_SM501; + + return sm501_register_device(sm, pdev); +} + +static int sm501_register_display(struct sm501_devdata *sm, + resource_size_t *mem_avail) +{ + struct platform_device *pdev; + + pdev = sm501_create_subdev(sm, "sm501-fb", 4, 0); + if (!pdev) + return -ENOMEM; + + sm501_create_subio(sm, &pdev->resource[0], 0x80000, 0x10000); + sm501_create_subio(sm, &pdev->resource[1], 0x100000, 0x50000); + sm501_create_mem(sm, &pdev->resource[2], mem_avail, *mem_avail); + sm501_create_irq(sm, &pdev->resource[3]); + + return sm501_register_device(sm, pdev); +} + +#ifdef CONFIG_MFD_SM501_GPIO + +static inline struct sm501_gpio_chip *to_sm501_gpio(struct gpio_chip *gc) +{ + return container_of(gc, struct sm501_gpio_chip, gpio); +} + +static inline struct sm501_devdata *sm501_gpio_to_dev(struct sm501_gpio *gpio) +{ + return container_of(gpio, struct sm501_devdata, gpio); +} + +static int sm501_gpio_get(struct gpio_chip *chip, unsigned offset) + +{ + struct sm501_gpio_chip *smgpio = to_sm501_gpio(chip); + unsigned long result; + + result = readl(smgpio->regbase + SM501_GPIO_DATA_LOW); + result >>= offset; + + return result & 1UL; +} + +static void sm501_gpio_ensure_gpio(struct sm501_gpio_chip *smchip, + unsigned long bit) +{ + unsigned long ctrl; + + /* check and modify if this pin is not set as gpio. */ + + if (readl(smchip->control) & bit) { + dev_info(sm501_gpio_to_dev(smchip->ourgpio)->dev, + "changing mode of gpio, bit %08lx\n", bit); + + ctrl = readl(smchip->control); + ctrl &= ~bit; + writel(ctrl, smchip->control); + + sm501_sync_regs(sm501_gpio_to_dev(smchip->ourgpio)); + } +} + +static void sm501_gpio_set(struct gpio_chip *chip, unsigned offset, int value) + +{ + struct sm501_gpio_chip *smchip = to_sm501_gpio(chip); + struct sm501_gpio *smgpio = smchip->ourgpio; + unsigned long bit = 1 << offset; + void __iomem *regs = smchip->regbase; + unsigned long save; + unsigned long val; + + dev_dbg(sm501_gpio_to_dev(smgpio)->dev, "%s(%p,%d)\n", + __func__, chip, offset); + + spin_lock_irqsave(&smgpio->lock, save); + + val = readl(regs + SM501_GPIO_DATA_LOW) & ~bit; + if (value) + val |= bit; + writel(val, regs); + + sm501_sync_regs(sm501_gpio_to_dev(smgpio)); + sm501_gpio_ensure_gpio(smchip, bit); + + spin_unlock_irqrestore(&smgpio->lock, save); +} + +static int sm501_gpio_input(struct gpio_chip *chip, unsigned offset) +{ + struct sm501_gpio_chip *smchip = to_sm501_gpio(chip); + struct sm501_gpio *smgpio = smchip->ourgpio; + void __iomem *regs = smchip->regbase; + unsigned long bit = 1 << offset; + unsigned long save; + unsigned long ddr; + + dev_dbg(sm501_gpio_to_dev(smgpio)->dev, "%s(%p,%d)\n", + __func__, chip, offset); + + spin_lock_irqsave(&smgpio->lock, save); + + ddr = readl(regs + SM501_GPIO_DDR_LOW); + writel(ddr & ~bit, regs + SM501_GPIO_DDR_LOW); + + sm501_sync_regs(sm501_gpio_to_dev(smgpio)); + sm501_gpio_ensure_gpio(smchip, bit); + + spin_unlock_irqrestore(&smgpio->lock, save); + + return 0; +} + +static int sm501_gpio_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct sm501_gpio_chip *smchip = to_sm501_gpio(chip); + struct sm501_gpio *smgpio = smchip->ourgpio; + unsigned long bit = 1 << offset; + void __iomem *regs = smchip->regbase; + unsigned long save; + unsigned long val; + unsigned long ddr; + + dev_dbg(sm501_gpio_to_dev(smgpio)->dev, "%s(%p,%d,%d)\n", + __func__, chip, offset, value); + + spin_lock_irqsave(&smgpio->lock, save); + + val = readl(regs + SM501_GPIO_DATA_LOW); + if (value) + val |= bit; + else + val &= ~bit; + writel(val, regs); + + ddr = readl(regs + SM501_GPIO_DDR_LOW); + writel(ddr | bit, regs + SM501_GPIO_DDR_LOW); + + sm501_sync_regs(sm501_gpio_to_dev(smgpio)); + writel(val, regs + SM501_GPIO_DATA_LOW); + + sm501_sync_regs(sm501_gpio_to_dev(smgpio)); + spin_unlock_irqrestore(&smgpio->lock, save); + + return 0; +} + +static struct gpio_chip gpio_chip_template = { + .ngpio = 32, + .direction_input = sm501_gpio_input, + .direction_output = sm501_gpio_output, + .set = sm501_gpio_set, + .get = sm501_gpio_get, +}; + +static int __devinit sm501_gpio_register_chip(struct sm501_devdata *sm, + struct sm501_gpio *gpio, + struct sm501_gpio_chip *chip) +{ + struct sm501_platdata *pdata = sm->platdata; + struct gpio_chip *gchip = &chip->gpio; + int base = pdata->gpio_base; + + chip->gpio = gpio_chip_template; + + if (chip == &gpio->high) { + if (base > 0) + base += 32; + chip->regbase = gpio->regs + SM501_GPIO_DATA_HIGH; + chip->control = sm->regs + SM501_GPIO63_32_CONTROL; + gchip->label = "SM501-HIGH"; + } else { + chip->regbase = gpio->regs + SM501_GPIO_DATA_LOW; + chip->control = sm->regs + SM501_GPIO31_0_CONTROL; + gchip->label = "SM501-LOW"; + } + + gchip->base = base; + chip->ourgpio = gpio; + + return gpiochip_add(gchip); +} + +static int __devinit sm501_register_gpio(struct sm501_devdata *sm) +{ + struct sm501_gpio *gpio = &sm->gpio; + resource_size_t iobase = sm->io_res->start + SM501_GPIO; + int ret; + int tmp; + + dev_dbg(sm->dev, "registering gpio block %08llx\n", + (unsigned long long)iobase); + + spin_lock_init(&gpio->lock); + + gpio->regs_res = request_mem_region(iobase, 0x20, "sm501-gpio"); + if (gpio->regs_res == NULL) { + dev_err(sm->dev, "gpio: failed to request region\n"); + return -ENXIO; + } + + gpio->regs = ioremap(iobase, 0x20); + if (gpio->regs == NULL) { + dev_err(sm->dev, "gpio: failed to remap registers\n"); + ret = -ENXIO; + goto err_claimed; + } + + /* Register both our chips. */ + + ret = sm501_gpio_register_chip(sm, gpio, &gpio->low); + if (ret) { + dev_err(sm->dev, "failed to add low chip\n"); + goto err_mapped; + } + + ret = sm501_gpio_register_chip(sm, gpio, &gpio->high); + if (ret) { + dev_err(sm->dev, "failed to add high chip\n"); + goto err_low_chip; + } + + gpio->registered = 1; + + return 0; + + err_low_chip: + tmp = gpiochip_remove(&gpio->low.gpio); + if (tmp) { + dev_err(sm->dev, "cannot remove low chip, cannot tidy up\n"); + return ret; + } + + err_mapped: + iounmap(gpio->regs); + + err_claimed: + release_resource(gpio->regs_res); + kfree(gpio->regs_res); + + return ret; +} + +static void sm501_gpio_remove(struct sm501_devdata *sm) +{ + struct sm501_gpio *gpio = &sm->gpio; + int ret; + + if (!sm->gpio.registered) + return; + + ret = gpiochip_remove(&gpio->low.gpio); + if (ret) + dev_err(sm->dev, "cannot remove low chip, cannot tidy up\n"); + + ret = gpiochip_remove(&gpio->high.gpio); + if (ret) + dev_err(sm->dev, "cannot remove high chip, cannot tidy up\n"); + + iounmap(gpio->regs); + release_resource(gpio->regs_res); + kfree(gpio->regs_res); +} + +static inline int sm501_gpio_pin2nr(struct sm501_devdata *sm, unsigned int pin) +{ + struct sm501_gpio *gpio = &sm->gpio; + int base = (pin < 32) ? gpio->low.gpio.base : gpio->high.gpio.base; + + return (pin % 32) + base; +} + +static inline int sm501_gpio_isregistered(struct sm501_devdata *sm) +{ + return sm->gpio.registered; +} +#else +static inline int sm501_register_gpio(struct sm501_devdata *sm) +{ + return 0; +} + +static inline void sm501_gpio_remove(struct sm501_devdata *sm) +{ +} + +static inline int sm501_gpio_pin2nr(struct sm501_devdata *sm, unsigned int pin) +{ + return -1; +} + +static inline int sm501_gpio_isregistered(struct sm501_devdata *sm) +{ + return 0; +} +#endif + +static int sm501_register_gpio_i2c_instance(struct sm501_devdata *sm, + struct sm501_platdata_gpio_i2c *iic) +{ + struct i2c_gpio_platform_data *icd; + struct platform_device *pdev; + + pdev = sm501_create_subdev(sm, "i2c-gpio", 0, + sizeof(struct i2c_gpio_platform_data)); + if (!pdev) + return -ENOMEM; + + icd = pdev->dev.platform_data; + + /* We keep the pin_sda and pin_scl fields relative in case the + * same platform data is passed to >1 SM501. + */ + + icd->sda_pin = sm501_gpio_pin2nr(sm, iic->pin_sda); + icd->scl_pin = sm501_gpio_pin2nr(sm, iic->pin_scl); + icd->timeout = iic->timeout; + icd->udelay = iic->udelay; + + /* note, we can't use either of the pin numbers, as the i2c-gpio + * driver uses the platform.id field to generate the bus number + * to register with the i2c core; The i2c core doesn't have enough + * entries to deal with anything we currently use. + */ + + pdev->id = iic->bus_num; + + dev_info(sm->dev, "registering i2c-%d: sda=%d (%d), scl=%d (%d)\n", + iic->bus_num, + icd->sda_pin, iic->pin_sda, icd->scl_pin, iic->pin_scl); + + return sm501_register_device(sm, pdev); +} + +static int sm501_register_gpio_i2c(struct sm501_devdata *sm, + struct sm501_platdata *pdata) +{ + struct sm501_platdata_gpio_i2c *iic = pdata->gpio_i2c; + int index; + int ret; + + for (index = 0; index < pdata->gpio_i2c_nr; index++, iic++) { + ret = sm501_register_gpio_i2c_instance(sm, iic); + if (ret < 0) + return ret; + } + + return 0; +} + +/* sm501_dbg_regs + * + * Debug attribute to attach to parent device to show core registers +*/ + +static ssize_t sm501_dbg_regs(struct device *dev, + struct device_attribute *attr, char *buff) +{ + struct sm501_devdata *sm = dev_get_drvdata(dev) ; + unsigned int reg; + char *ptr = buff; + int ret; + + for (reg = 0x00; reg < 0x70; reg += 4) { + ret = sprintf(ptr, "%08x = %08x\n", + reg, readl(sm->regs + reg)); + ptr += ret; + } + + return ptr - buff; +} + + +static DEVICE_ATTR(dbg_regs, 0666, sm501_dbg_regs, NULL); + +/* sm501_init_reg + * + * Helper function for the init code to setup a register + * + * clear the bits which are set in r->mask, and then set + * the bits set in r->set. +*/ + +static inline void sm501_init_reg(struct sm501_devdata *sm, + unsigned long reg, + struct sm501_reg_init *r) +{ + unsigned long tmp; + + tmp = readl(sm->regs + reg); + tmp &= ~r->mask; + tmp |= r->set; + writel(tmp, sm->regs + reg); +} + +/* sm501_init_regs + * + * Setup core register values +*/ + +static void sm501_init_regs(struct sm501_devdata *sm, + struct sm501_initdata *init) +{ + sm501_misc_control(sm->dev, + init->misc_control.set, + init->misc_control.mask); + + sm501_init_reg(sm, SM501_MISC_TIMING, &init->misc_timing); + sm501_init_reg(sm, SM501_GPIO31_0_CONTROL, &init->gpio_low); + sm501_init_reg(sm, SM501_GPIO63_32_CONTROL, &init->gpio_high); + + if (init->m1xclk) { + dev_info(sm->dev, "setting M1XCLK to %ld\n", init->m1xclk); + sm501_set_clock(sm->dev, SM501_CLOCK_M1XCLK, init->m1xclk); + } + + if (init->mclk) { + dev_info(sm->dev, "setting MCLK to %ld\n", init->mclk); + sm501_set_clock(sm->dev, SM501_CLOCK_MCLK, init->mclk); + } + +} + +/* Check the PLL sources for the M1CLK and M1XCLK + * + * If the M1CLK and M1XCLKs are not sourced from the same PLL, then + * there is a risk (see errata AB-5) that the SM501 will cease proper + * function. If this happens, then it is likely the SM501 will + * hang the system. +*/ + +static int sm501_check_clocks(struct sm501_devdata *sm) +{ + unsigned long pwrmode = readl(sm->regs + SM501_CURRENT_CLOCK); + unsigned long msrc = (pwrmode & SM501_POWERMODE_M_SRC); + unsigned long m1src = (pwrmode & SM501_POWERMODE_M1_SRC); + + return ((msrc == 0 && m1src != 0) || (msrc != 0 && m1src == 0)); +} + +static unsigned int sm501_mem_local[] = { + [0] = 4*1024*1024, + [1] = 8*1024*1024, + [2] = 16*1024*1024, + [3] = 32*1024*1024, + [4] = 64*1024*1024, + [5] = 2*1024*1024, +}; + +/* sm501_init_dev + * + * Common init code for an SM501 +*/ + +static int __devinit sm501_init_dev(struct sm501_devdata *sm) +{ + struct sm501_initdata *idata; + struct sm501_platdata *pdata; + resource_size_t mem_avail; + unsigned long dramctrl; + unsigned long devid; + int ret; + + mutex_init(&sm->clock_lock); + spin_lock_init(&sm->reg_lock); + + INIT_LIST_HEAD(&sm->devices); + + devid = readl(sm->regs + SM501_DEVICEID); + + if ((devid & SM501_DEVICEID_IDMASK) != SM501_DEVICEID_SM501) { + dev_err(sm->dev, "incorrect device id %08lx\n", devid); + return -EINVAL; + } + + /* disable irqs */ + writel(0, sm->regs + SM501_IRQ_MASK); + + dramctrl = readl(sm->regs + SM501_DRAM_CONTROL); + mem_avail = sm501_mem_local[(dramctrl >> 13) & 0x7]; + + dev_info(sm->dev, "SM501 At %p: Version %08lx, %ld Mb, IRQ %d\n", + sm->regs, devid, (unsigned long)mem_avail >> 20, sm->irq); + + sm->rev = devid & SM501_DEVICEID_REVMASK; + + sm501_dump_gate(sm); + + ret = device_create_file(sm->dev, &dev_attr_dbg_regs); + if (ret) + dev_err(sm->dev, "failed to create debug regs file\n"); + + sm501_dump_clk(sm); + + /* check to see if we have some device initialisation */ + + pdata = sm->platdata; + idata = pdata ? pdata->init : NULL; + + if (idata) { + sm501_init_regs(sm, idata); + + if (idata->devices & SM501_USE_USB_HOST) + sm501_register_usbhost(sm, &mem_avail); + if (idata->devices & (SM501_USE_UART0 | SM501_USE_UART1)) + sm501_register_uart(sm, idata->devices); + if (idata->devices & SM501_USE_GPIO) + sm501_register_gpio(sm); + } + + if (pdata->gpio_i2c != NULL && pdata->gpio_i2c_nr > 0) { + if (!sm501_gpio_isregistered(sm)) + dev_err(sm->dev, "no gpio available for i2c gpio.\n"); + else + sm501_register_gpio_i2c(sm, pdata); + } + + ret = sm501_check_clocks(sm); + if (ret) { + dev_err(sm->dev, "M1X and M clocks sourced from different " + "PLLs\n"); + return -EINVAL; + } + + /* always create a framebuffer */ + sm501_register_display(sm, &mem_avail); + + return 0; +} + +static int __devinit sm501_plat_probe(struct platform_device *dev) +{ + struct sm501_devdata *sm; + int ret; + + sm = kzalloc(sizeof(struct sm501_devdata), GFP_KERNEL); + if (sm == NULL) { + dev_err(&dev->dev, "no memory for device data\n"); + ret = -ENOMEM; + goto err1; + } + + sm->dev = &dev->dev; + sm->pdev_id = dev->id; + sm->platdata = dev->dev.platform_data; + + ret = platform_get_irq(dev, 0); + if (ret < 0) { + dev_err(&dev->dev, "failed to get irq resource\n"); + goto err_res; + } + sm->irq = ret; + + sm->io_res = platform_get_resource(dev, IORESOURCE_MEM, 1); + sm->mem_res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (sm->io_res == NULL || sm->mem_res == NULL) { + dev_err(&dev->dev, "failed to get IO resource\n"); + ret = -ENOENT; + goto err_res; + } + + sm->regs_claim = request_mem_region(sm->io_res->start, + 0x100, "sm501"); + + if (sm->regs_claim == NULL) { + dev_err(&dev->dev, "cannot claim registers\n"); + ret = -EBUSY; + goto err_res; + } + + platform_set_drvdata(dev, sm); + + sm->regs = ioremap(sm->io_res->start, + (sm->io_res->end - sm->io_res->start) - 1); + + if (sm->regs == NULL) { + dev_err(&dev->dev, "cannot remap registers\n"); + ret = -EIO; + goto err_claim; + } + + return sm501_init_dev(sm); + + err_claim: + release_resource(sm->regs_claim); + kfree(sm->regs_claim); + err_res: + kfree(sm); + err1: + return ret; + +} + +#ifdef CONFIG_PM + +/* power management support */ + +static void sm501_set_power(struct sm501_devdata *sm, int on) +{ + struct sm501_platdata *pd = sm->platdata; + + if (pd == NULL) + return; + + if (pd->get_power) { + if (pd->get_power(sm->dev) == on) { + dev_dbg(sm->dev, "is already %d\n", on); + return; + } + } + + if (pd->set_power) { + dev_dbg(sm->dev, "setting power to %d\n", on); + + pd->set_power(sm->dev, on); + sm501_mdelay(sm, 10); + } +} + +static int sm501_plat_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct sm501_devdata *sm = platform_get_drvdata(pdev); + + sm->in_suspend = 1; + sm->pm_misc = readl(sm->regs + SM501_MISC_CONTROL); + + sm501_dump_regs(sm); + + if (sm->platdata) { + if (sm->platdata->flags & SM501_FLAG_SUSPEND_OFF) + sm501_set_power(sm, 0); + } + + return 0; +} + +static int sm501_plat_resume(struct platform_device *pdev) +{ + struct sm501_devdata *sm = platform_get_drvdata(pdev); + + sm501_set_power(sm, 1); + + sm501_dump_regs(sm); + sm501_dump_gate(sm); + sm501_dump_clk(sm); + + /* check to see if we are in the same state as when suspended */ + + if (readl(sm->regs + SM501_MISC_CONTROL) != sm->pm_misc) { + dev_info(sm->dev, "SM501_MISC_CONTROL changed over sleep\n"); + writel(sm->pm_misc, sm->regs + SM501_MISC_CONTROL); + + /* our suspend causes the controller state to change, + * either by something attempting setup, power loss, + * or an external reset event on power change */ + + if (sm->platdata && sm->platdata->init) { + sm501_init_regs(sm, sm->platdata->init); + } + } + + /* dump our state from resume */ + + sm501_dump_regs(sm); + sm501_dump_clk(sm); + + sm->in_suspend = 0; + + return 0; +} +#else +#define sm501_plat_suspend NULL +#define sm501_plat_resume NULL +#endif + +/* Initialisation data for PCI devices */ + +static struct sm501_initdata sm501_pci_initdata = { + .gpio_high = { + .set = 0x3F000000, /* 24bit panel */ + .mask = 0x0, + }, + .misc_timing = { + .set = 0x010100, /* SDRAM timing */ + .mask = 0x1F1F00, + }, + .misc_control = { + .set = SM501_MISC_PNL_24BIT, + .mask = 0, + }, + + .devices = SM501_USE_ALL, + + /* Errata AB-3 says that 72MHz is the fastest available + * for 33MHZ PCI with proper bus-mastering operation */ + + .mclk = 72 * MHZ, + .m1xclk = 144 * MHZ, +}; + +static struct sm501_platdata_fbsub sm501_pdata_fbsub = { + .flags = (SM501FB_FLAG_USE_INIT_MODE | + SM501FB_FLAG_USE_HWCURSOR | + SM501FB_FLAG_USE_HWACCEL | + SM501FB_FLAG_DISABLE_AT_EXIT), +}; + +static struct sm501_platdata_fb sm501_fb_pdata = { + .fb_route = SM501_FB_OWN, + .fb_crt = &sm501_pdata_fbsub, + .fb_pnl = &sm501_pdata_fbsub, +}; + +static struct sm501_platdata sm501_pci_platdata = { + .init = &sm501_pci_initdata, + .fb = &sm501_fb_pdata, + .gpio_base = -1, +}; + +static int __devinit sm501_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct sm501_devdata *sm; + int err; + + sm = kzalloc(sizeof(struct sm501_devdata), GFP_KERNEL); + if (sm == NULL) { + dev_err(&dev->dev, "no memory for device data\n"); + err = -ENOMEM; + goto err1; + } + + /* set a default set of platform data */ + dev->dev.platform_data = sm->platdata = &sm501_pci_platdata; + + /* set a hopefully unique id for our child platform devices */ + sm->pdev_id = 32 + dev->devfn; + + pci_set_drvdata(dev, sm); + + err = pci_enable_device(dev); + if (err) { + dev_err(&dev->dev, "cannot enable device\n"); + goto err2; + } + + sm->dev = &dev->dev; + sm->irq = dev->irq; + +#ifdef __BIG_ENDIAN + /* if the system is big-endian, we most probably have a + * translation in the IO layer making the PCI bus little endian + * so make the framebuffer swapped pixels */ + + sm501_fb_pdata.flags |= SM501_FBPD_SWAP_FB_ENDIAN; +#endif + + /* check our resources */ + + if (!(pci_resource_flags(dev, 0) & IORESOURCE_MEM)) { + dev_err(&dev->dev, "region #0 is not memory?\n"); + err = -EINVAL; + goto err3; + } + + if (!(pci_resource_flags(dev, 1) & IORESOURCE_MEM)) { + dev_err(&dev->dev, "region #1 is not memory?\n"); + err = -EINVAL; + goto err3; + } + + /* make our resources ready for sharing */ + + sm->io_res = &dev->resource[1]; + sm->mem_res = &dev->resource[0]; + + sm->regs_claim = request_mem_region(sm->io_res->start, + 0x100, "sm501"); + if (sm->regs_claim == NULL) { + dev_err(&dev->dev, "cannot claim registers\n"); + err= -EBUSY; + goto err3; + } + + sm->regs = pci_ioremap_bar(dev, 1); + + if (sm->regs == NULL) { + dev_err(&dev->dev, "cannot remap registers\n"); + err = -EIO; + goto err4; + } + + sm501_init_dev(sm); + return 0; + + err4: + release_resource(sm->regs_claim); + kfree(sm->regs_claim); + err3: + pci_disable_device(dev); + err2: + pci_set_drvdata(dev, NULL); + kfree(sm); + err1: + return err; +} + +static void sm501_remove_sub(struct sm501_devdata *sm, + struct sm501_device *smdev) +{ + list_del(&smdev->list); + platform_device_unregister(&smdev->pdev); +} + +static void sm501_dev_remove(struct sm501_devdata *sm) +{ + struct sm501_device *smdev, *tmp; + + list_for_each_entry_safe(smdev, tmp, &sm->devices, list) + sm501_remove_sub(sm, smdev); + + device_remove_file(sm->dev, &dev_attr_dbg_regs); + + sm501_gpio_remove(sm); +} + +static void __devexit sm501_pci_remove(struct pci_dev *dev) +{ + struct sm501_devdata *sm = pci_get_drvdata(dev); + + sm501_dev_remove(sm); + iounmap(sm->regs); + + release_resource(sm->regs_claim); + kfree(sm->regs_claim); + + pci_set_drvdata(dev, NULL); + pci_disable_device(dev); +} + +static int sm501_plat_remove(struct platform_device *dev) +{ + struct sm501_devdata *sm = platform_get_drvdata(dev); + + sm501_dev_remove(sm); + iounmap(sm->regs); + + release_resource(sm->regs_claim); + kfree(sm->regs_claim); + + return 0; +} + +static struct pci_device_id sm501_pci_tbl[] = { + { 0x126f, 0x0501, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, sm501_pci_tbl); + +static struct pci_driver sm501_pci_driver = { + .name = "sm501", + .id_table = sm501_pci_tbl, + .probe = sm501_pci_probe, + .remove = __devexit_p(sm501_pci_remove), +}; + +MODULE_ALIAS("platform:sm501"); + +static struct platform_driver sm501_plat_driver = { + .driver = { + .name = "sm501", + .owner = THIS_MODULE, + }, + .probe = sm501_plat_probe, + .remove = sm501_plat_remove, + .suspend = sm501_plat_suspend, + .resume = sm501_plat_resume, +}; + +static int __init sm501_base_init(void) +{ + platform_driver_register(&sm501_plat_driver); + return pci_register_driver(&sm501_pci_driver); +} + +static void __exit sm501_base_exit(void) +{ + platform_driver_unregister(&sm501_plat_driver); + pci_unregister_driver(&sm501_pci_driver); +} + +module_init(sm501_base_init); +module_exit(sm501_base_exit); + +MODULE_DESCRIPTION("SM501 Core Driver"); +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, Vincent Sanders"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c new file mode 100644 index 00000000000..bcf4687d4af --- /dev/null +++ b/drivers/mfd/t7l66xb.c @@ -0,0 +1,467 @@ +/* + * + * Toshiba T7L66XB core mfd support + * + * Copyright (c) 2005, 2007, 2008 Ian Molton + * Copyright (c) 2008 Dmitry Baryshkov + * + * 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. + * + * T7L66 features: + * + * Supported in this driver: + * SD/MMC + * SM/NAND flash controller + * + * As yet not supported + * GPIO interface (on NAND pins) + * Serial interface + * TFT 'interface converter' + * PCMCIA interface logic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tmio.h> +#include <linux/mfd/t7l66xb.h> + +enum { + T7L66XB_CELL_NAND, + T7L66XB_CELL_MMC, +}; + +static const struct resource t7l66xb_mmc_resources[] = { + { + .start = 0x800, + .end = 0x9ff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_T7L66XB_MMC, + .end = IRQ_T7L66XB_MMC, + .flags = IORESOURCE_IRQ, + }, +}; + +#define SCR_REVID 0x08 /* b Revision ID */ +#define SCR_IMR 0x42 /* b Interrupt Mask */ +#define SCR_DEV_CTL 0xe0 /* b Device control */ +#define SCR_ISR 0xe1 /* b Interrupt Status */ +#define SCR_GPO_OC 0xf0 /* b GPO output control */ +#define SCR_GPO_OS 0xf1 /* b GPO output enable */ +#define SCR_GPI_S 0xf2 /* w GPI status */ +#define SCR_APDC 0xf8 /* b Active pullup down ctrl */ + +#define SCR_DEV_CTL_USB BIT(0) /* USB enable */ +#define SCR_DEV_CTL_MMC BIT(1) /* MMC enable */ + +/*--------------------------------------------------------------------------*/ + +struct t7l66xb { + void __iomem *scr; + /* Lock to protect registers requiring read/modify/write ops. */ + spinlock_t lock; + + struct resource rscr; + struct clk *clk48m; + struct clk *clk32k; + int irq; + int irq_base; +}; + +/*--------------------------------------------------------------------------*/ + +static int t7l66xb_mmc_enable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + unsigned long flags; + u8 dev_ctl; + + clk_enable(t7l66xb->clk32k); + + spin_lock_irqsave(&t7l66xb->lock, flags); + + dev_ctl = tmio_ioread8(t7l66xb->scr + SCR_DEV_CTL); + dev_ctl |= SCR_DEV_CTL_MMC; + tmio_iowrite8(dev_ctl, t7l66xb->scr + SCR_DEV_CTL); + + spin_unlock_irqrestore(&t7l66xb->lock, flags); + + tmio_core_mmc_enable(t7l66xb->scr + 0x200, 0, + t7l66xb_mmc_resources[0].start & 0xfffe); + + return 0; +} + +static int t7l66xb_mmc_disable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + unsigned long flags; + u8 dev_ctl; + + spin_lock_irqsave(&t7l66xb->lock, flags); + + dev_ctl = tmio_ioread8(t7l66xb->scr + SCR_DEV_CTL); + dev_ctl &= ~SCR_DEV_CTL_MMC; + tmio_iowrite8(dev_ctl, t7l66xb->scr + SCR_DEV_CTL); + + spin_unlock_irqrestore(&t7l66xb->lock, flags); + + clk_disable(t7l66xb->clk32k); + + return 0; +} + +static void t7l66xb_mmc_pwr(struct platform_device *mmc, int state) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + + tmio_core_mmc_pwr(t7l66xb->scr + 0x200, 0, state); +} + +static void t7l66xb_mmc_clk_div(struct platform_device *mmc, int state) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + + tmio_core_mmc_clk_div(t7l66xb->scr + 0x200, 0, state); +} + +/*--------------------------------------------------------------------------*/ + +static struct tmio_mmc_data t7166xb_mmc_data = { + .hclk = 24000000, + .set_pwr = t7l66xb_mmc_pwr, + .set_clk_div = t7l66xb_mmc_clk_div, +}; + +static const struct resource t7l66xb_nand_resources[] = { + { + .start = 0xc00, + .end = 0xc07, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x0100, + .end = 0x01ff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_T7L66XB_NAND, + .end = IRQ_T7L66XB_NAND, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell t7l66xb_cells[] = { + [T7L66XB_CELL_MMC] = { + .name = "tmio-mmc", + .enable = t7l66xb_mmc_enable, + .disable = t7l66xb_mmc_disable, + .driver_data = &t7166xb_mmc_data, + .num_resources = ARRAY_SIZE(t7l66xb_mmc_resources), + .resources = t7l66xb_mmc_resources, + }, + [T7L66XB_CELL_NAND] = { + .name = "tmio-nand", + .num_resources = ARRAY_SIZE(t7l66xb_nand_resources), + .resources = t7l66xb_nand_resources, + }, +}; + +/*--------------------------------------------------------------------------*/ + +/* Handle the T7L66XB interrupt mux */ +static void t7l66xb_irq(unsigned int irq, struct irq_desc *desc) +{ + struct t7l66xb *t7l66xb = get_irq_data(irq); + unsigned int isr; + unsigned int i, irq_base; + + irq_base = t7l66xb->irq_base; + + while ((isr = tmio_ioread8(t7l66xb->scr + SCR_ISR) & + ~tmio_ioread8(t7l66xb->scr + SCR_IMR))) + for (i = 0; i < T7L66XB_NR_IRQS; i++) + if (isr & (1 << i)) + generic_handle_irq(irq_base + i); +} + +static void t7l66xb_irq_mask(unsigned int irq) +{ + struct t7l66xb *t7l66xb = get_irq_chip_data(irq); + unsigned long flags; + u8 imr; + + spin_lock_irqsave(&t7l66xb->lock, flags); + imr = tmio_ioread8(t7l66xb->scr + SCR_IMR); + imr |= 1 << (irq - t7l66xb->irq_base); + tmio_iowrite8(imr, t7l66xb->scr + SCR_IMR); + spin_unlock_irqrestore(&t7l66xb->lock, flags); +} + +static void t7l66xb_irq_unmask(unsigned int irq) +{ + struct t7l66xb *t7l66xb = get_irq_chip_data(irq); + unsigned long flags; + u8 imr; + + spin_lock_irqsave(&t7l66xb->lock, flags); + imr = tmio_ioread8(t7l66xb->scr + SCR_IMR); + imr &= ~(1 << (irq - t7l66xb->irq_base)); + tmio_iowrite8(imr, t7l66xb->scr + SCR_IMR); + spin_unlock_irqrestore(&t7l66xb->lock, flags); +} + +static struct irq_chip t7l66xb_chip = { + .name = "t7l66xb", + .ack = t7l66xb_irq_mask, + .mask = t7l66xb_irq_mask, + .unmask = t7l66xb_irq_unmask, +}; + +/*--------------------------------------------------------------------------*/ + +/* Install the IRQ handler */ +static void t7l66xb_attach_irq(struct platform_device *dev) +{ + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + unsigned int irq, irq_base; + + irq_base = t7l66xb->irq_base; + + for (irq = irq_base; irq < irq_base + T7L66XB_NR_IRQS; irq++) { + set_irq_chip(irq, &t7l66xb_chip); + set_irq_chip_data(irq, t7l66xb); + set_irq_handler(irq, handle_level_irq); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); +#endif + } + + set_irq_type(t7l66xb->irq, IRQ_TYPE_EDGE_FALLING); + set_irq_data(t7l66xb->irq, t7l66xb); + set_irq_chained_handler(t7l66xb->irq, t7l66xb_irq); +} + +static void t7l66xb_detach_irq(struct platform_device *dev) +{ + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + unsigned int irq, irq_base; + + irq_base = t7l66xb->irq_base; + + set_irq_chained_handler(t7l66xb->irq, NULL); + set_irq_data(t7l66xb->irq, NULL); + + for (irq = irq_base; irq < irq_base + T7L66XB_NR_IRQS; irq++) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + set_irq_chip(irq, NULL); + set_irq_chip_data(irq, NULL); + } +} + +/*--------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM +static int t7l66xb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + struct t7l66xb_platform_data *pdata = dev->dev.platform_data; + + if (pdata && pdata->suspend) + pdata->suspend(dev); + clk_disable(t7l66xb->clk48m); + + return 0; +} + +static int t7l66xb_resume(struct platform_device *dev) +{ + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + struct t7l66xb_platform_data *pdata = dev->dev.platform_data; + + clk_enable(t7l66xb->clk48m); + if (pdata && pdata->resume) + pdata->resume(dev); + + tmio_core_mmc_enable(t7l66xb->scr + 0x200, 0, + t7l66xb_mmc_resources[0].start & 0xfffe); + + return 0; +} +#else +#define t7l66xb_suspend NULL +#define t7l66xb_resume NULL +#endif + +/*--------------------------------------------------------------------------*/ + +static int t7l66xb_probe(struct platform_device *dev) +{ + struct t7l66xb_platform_data *pdata = dev->dev.platform_data; + struct t7l66xb *t7l66xb; + struct resource *iomem, *rscr; + int ret; + + iomem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!iomem) + return -EINVAL; + + t7l66xb = kzalloc(sizeof *t7l66xb, GFP_KERNEL); + if (!t7l66xb) + return -ENOMEM; + + spin_lock_init(&t7l66xb->lock); + + platform_set_drvdata(dev, t7l66xb); + + ret = platform_get_irq(dev, 0); + if (ret >= 0) + t7l66xb->irq = ret; + else + goto err_noirq; + + t7l66xb->irq_base = pdata->irq_base; + + t7l66xb->clk32k = clk_get(&dev->dev, "CLK_CK32K"); + if (IS_ERR(t7l66xb->clk32k)) { + ret = PTR_ERR(t7l66xb->clk32k); + goto err_clk32k_get; + } + + t7l66xb->clk48m = clk_get(&dev->dev, "CLK_CK48M"); + if (IS_ERR(t7l66xb->clk48m)) { + ret = PTR_ERR(t7l66xb->clk48m); + clk_put(t7l66xb->clk32k); + goto err_clk48m_get; + } + + rscr = &t7l66xb->rscr; + rscr->name = "t7l66xb-core"; + rscr->start = iomem->start; + rscr->end = iomem->start + 0xff; + rscr->flags = IORESOURCE_MEM; + + ret = request_resource(iomem, rscr); + if (ret) + goto err_request_scr; + + t7l66xb->scr = ioremap(rscr->start, rscr->end - rscr->start + 1); + if (!t7l66xb->scr) { + ret = -ENOMEM; + goto err_ioremap; + } + + clk_enable(t7l66xb->clk48m); + + if (pdata && pdata->enable) + pdata->enable(dev); + + /* Mask all interrupts */ + tmio_iowrite8(0xbf, t7l66xb->scr + SCR_IMR); + + printk(KERN_INFO "%s rev %d @ 0x%08lx, irq %d\n", + dev->name, tmio_ioread8(t7l66xb->scr + SCR_REVID), + (unsigned long)iomem->start, t7l66xb->irq); + + t7l66xb_attach_irq(dev); + + t7l66xb_cells[T7L66XB_CELL_NAND].driver_data = pdata->nand_data; + t7l66xb_cells[T7L66XB_CELL_NAND].platform_data = + &t7l66xb_cells[T7L66XB_CELL_NAND]; + t7l66xb_cells[T7L66XB_CELL_NAND].data_size = + sizeof(t7l66xb_cells[T7L66XB_CELL_NAND]); + + t7l66xb_cells[T7L66XB_CELL_MMC].platform_data = + &t7l66xb_cells[T7L66XB_CELL_MMC]; + t7l66xb_cells[T7L66XB_CELL_MMC].data_size = + sizeof(t7l66xb_cells[T7L66XB_CELL_MMC]); + + ret = mfd_add_devices(&dev->dev, dev->id, + t7l66xb_cells, ARRAY_SIZE(t7l66xb_cells), + iomem, t7l66xb->irq_base); + + if (!ret) + return 0; + + t7l66xb_detach_irq(dev); + iounmap(t7l66xb->scr); +err_ioremap: + release_resource(&t7l66xb->rscr); +err_request_scr: + kfree(t7l66xb); + clk_put(t7l66xb->clk48m); +err_clk48m_get: + clk_put(t7l66xb->clk32k); +err_clk32k_get: +err_noirq: + return ret; +} + +static int t7l66xb_remove(struct platform_device *dev) +{ + struct t7l66xb_platform_data *pdata = dev->dev.platform_data; + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + int ret; + + ret = pdata->disable(dev); + clk_disable(t7l66xb->clk48m); + clk_put(t7l66xb->clk48m); + t7l66xb_detach_irq(dev); + iounmap(t7l66xb->scr); + release_resource(&t7l66xb->rscr); + mfd_remove_devices(&dev->dev); + platform_set_drvdata(dev, NULL); + kfree(t7l66xb); + + return ret; + +} + +static struct platform_driver t7l66xb_platform_driver = { + .driver = { + .name = "t7l66xb", + .owner = THIS_MODULE, + }, + .suspend = t7l66xb_suspend, + .resume = t7l66xb_resume, + .probe = t7l66xb_probe, + .remove = t7l66xb_remove, +}; + +/*--------------------------------------------------------------------------*/ + +static int __init t7l66xb_init(void) +{ + int retval = 0; + + retval = platform_driver_register(&t7l66xb_platform_driver); + return retval; +} + +static void __exit t7l66xb_exit(void) +{ + platform_driver_unregister(&t7l66xb_platform_driver); +} + +module_init(t7l66xb_init); +module_exit(t7l66xb_exit); + +MODULE_DESCRIPTION("Toshiba T7L66XB core driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ian Molton"); +MODULE_ALIAS("platform:t7l66xb"); diff --git a/drivers/mfd/tc6387xb.c b/drivers/mfd/tc6387xb.c new file mode 100644 index 00000000000..5c7f04343d5 --- /dev/null +++ b/drivers/mfd/tc6387xb.c @@ -0,0 +1,254 @@ +/* + * Toshiba TC6387XB support + * Copyright (c) 2005 Ian Molton + * + * 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. + * + * This file contains TC6387XB base support. + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tmio.h> +#include <linux/mfd/tc6387xb.h> + +enum { + TC6387XB_CELL_MMC, +}; + +struct tc6387xb { + void __iomem *scr; + struct clk *clk32k; + struct resource rscr; +}; + +static struct resource tc6387xb_mmc_resources[] = { + { + .start = 0x800, + .end = 0x9ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +/*--------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM +static int tc6387xb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + struct tc6387xb_platform_data *pdata = dev->dev.platform_data; + + if (pdata && pdata->suspend) + pdata->suspend(dev); + clk_disable(tc6387xb->clk32k); + + return 0; +} + +static int tc6387xb_resume(struct platform_device *dev) +{ + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + struct tc6387xb_platform_data *pdata = dev->dev.platform_data; + + clk_enable(tc6387xb->clk32k); + if (pdata && pdata->resume) + pdata->resume(dev); + + tmio_core_mmc_resume(tc6387xb->scr + 0x200, 0, + tc6387xb_mmc_resources[0].start & 0xfffe); + + return 0; +} +#else +#define tc6387xb_suspend NULL +#define tc6387xb_resume NULL +#endif + +/*--------------------------------------------------------------------------*/ + +static void tc6387xb_mmc_pwr(struct platform_device *mmc, int state) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + + tmio_core_mmc_pwr(tc6387xb->scr + 0x200, 0, state); +} + +static void tc6387xb_mmc_clk_div(struct platform_device *mmc, int state) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + + tmio_core_mmc_clk_div(tc6387xb->scr + 0x200, 0, state); +} + + +static int tc6387xb_mmc_enable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + + clk_enable(tc6387xb->clk32k); + + tmio_core_mmc_enable(tc6387xb->scr + 0x200, 0, + tc6387xb_mmc_resources[0].start & 0xfffe); + + return 0; +} + +static int tc6387xb_mmc_disable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + + clk_disable(tc6387xb->clk32k); + + return 0; +} + +static struct tmio_mmc_data tc6387xb_mmc_data = { + .hclk = 24000000, + .set_pwr = tc6387xb_mmc_pwr, + .set_clk_div = tc6387xb_mmc_clk_div, +}; + +/*--------------------------------------------------------------------------*/ + +static struct mfd_cell tc6387xb_cells[] = { + [TC6387XB_CELL_MMC] = { + .name = "tmio-mmc", + .enable = tc6387xb_mmc_enable, + .disable = tc6387xb_mmc_disable, + .driver_data = &tc6387xb_mmc_data, + .num_resources = ARRAY_SIZE(tc6387xb_mmc_resources), + .resources = tc6387xb_mmc_resources, + }, +}; + +static int tc6387xb_probe(struct platform_device *dev) +{ + struct tc6387xb_platform_data *pdata = dev->dev.platform_data; + struct resource *iomem, *rscr; + struct clk *clk32k; + struct tc6387xb *tc6387xb; + int irq, ret; + + iomem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!iomem) { + return -EINVAL; + } + + tc6387xb = kzalloc(sizeof *tc6387xb, GFP_KERNEL); + if (!tc6387xb) + return -ENOMEM; + + ret = platform_get_irq(dev, 0); + if (ret >= 0) + irq = ret; + else + goto err_no_irq; + + clk32k = clk_get(&dev->dev, "CLK_CK32K"); + if (IS_ERR(clk32k)) { + ret = PTR_ERR(clk32k); + goto err_no_clk; + } + + rscr = &tc6387xb->rscr; + rscr->name = "tc6387xb-core"; + rscr->start = iomem->start; + rscr->end = iomem->start + 0xff; + rscr->flags = IORESOURCE_MEM; + + ret = request_resource(iomem, rscr); + if (ret) + goto err_resource; + + tc6387xb->scr = ioremap(rscr->start, rscr->end - rscr->start + 1); + if (!tc6387xb->scr) { + ret = -ENOMEM; + goto err_ioremap; + } + + tc6387xb->clk32k = clk32k; + platform_set_drvdata(dev, tc6387xb); + + if (pdata && pdata->enable) + pdata->enable(dev); + + printk(KERN_INFO "Toshiba tc6387xb initialised\n"); + + tc6387xb_cells[TC6387XB_CELL_MMC].platform_data = + &tc6387xb_cells[TC6387XB_CELL_MMC]; + tc6387xb_cells[TC6387XB_CELL_MMC].data_size = + sizeof(tc6387xb_cells[TC6387XB_CELL_MMC]); + + ret = mfd_add_devices(&dev->dev, dev->id, tc6387xb_cells, + ARRAY_SIZE(tc6387xb_cells), iomem, irq); + + if (!ret) + return 0; + +err_ioremap: + release_resource(&tc6387xb->rscr); +err_resource: + clk_put(clk32k); +err_no_clk: +err_no_irq: + kfree(tc6387xb); + return ret; +} + +static int tc6387xb_remove(struct platform_device *dev) +{ + struct clk *clk32k = platform_get_drvdata(dev); + + mfd_remove_devices(&dev->dev); + clk_disable(clk32k); + clk_put(clk32k); + platform_set_drvdata(dev, NULL); + + return 0; +} + + +static struct platform_driver tc6387xb_platform_driver = { + .driver = { + .name = "tc6387xb", + }, + .probe = tc6387xb_probe, + .remove = tc6387xb_remove, + .suspend = tc6387xb_suspend, + .resume = tc6387xb_resume, +}; + + +static int __init tc6387xb_init(void) +{ + return platform_driver_register(&tc6387xb_platform_driver); +} + +static void __exit tc6387xb_exit(void) +{ + platform_driver_unregister(&tc6387xb_platform_driver); +} + +module_init(tc6387xb_init); +module_exit(tc6387xb_exit); + +MODULE_DESCRIPTION("Toshiba TC6387XB core driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ian Molton"); +MODULE_ALIAS("platform:tc6387xb"); + diff --git a/drivers/mfd/tc6393xb.c b/drivers/mfd/tc6393xb.c new file mode 100644 index 00000000000..4bc5a08a2b0 --- /dev/null +++ b/drivers/mfd/tc6393xb.c @@ -0,0 +1,876 @@ +/* + * Toshiba TC6393XB SoC support + * + * Copyright(c) 2005-2006 Chris Humbert + * Copyright(c) 2005 Dirk Opfer + * Copyright(c) 2005 Ian Molton <spyro@f2s.com> + * Copyright(c) 2007 Dmitry Baryshkov + * + * Based on code written by Sharp/Lineo for 2.4 kernels + * Based on locomo.c + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tmio.h> +#include <linux/mfd/tc6393xb.h> +#include <linux/gpio.h> + +#define SCR_REVID 0x08 /* b Revision ID */ +#define SCR_ISR 0x50 /* b Interrupt Status */ +#define SCR_IMR 0x52 /* b Interrupt Mask */ +#define SCR_IRR 0x54 /* b Interrupt Routing */ +#define SCR_GPER 0x60 /* w GP Enable */ +#define SCR_GPI_SR(i) (0x64 + (i)) /* b3 GPI Status */ +#define SCR_GPI_IMR(i) (0x68 + (i)) /* b3 GPI INT Mask */ +#define SCR_GPI_EDER(i) (0x6c + (i)) /* b3 GPI Edge Detect Enable */ +#define SCR_GPI_LIR(i) (0x70 + (i)) /* b3 GPI Level Invert */ +#define SCR_GPO_DSR(i) (0x78 + (i)) /* b3 GPO Data Set */ +#define SCR_GPO_DOECR(i) (0x7c + (i)) /* b3 GPO Data OE Control */ +#define SCR_GP_IARCR(i) (0x80 + (i)) /* b3 GP Internal Active Register Control */ +#define SCR_GP_IARLCR(i) (0x84 + (i)) /* b3 GP INTERNAL Active Register Level Control */ +#define SCR_GPI_BCR(i) (0x88 + (i)) /* b3 GPI Buffer Control */ +#define SCR_GPA_IARCR 0x8c /* w GPa Internal Active Register Control */ +#define SCR_GPA_IARLCR 0x90 /* w GPa Internal Active Register Level Control */ +#define SCR_GPA_BCR 0x94 /* w GPa Buffer Control */ +#define SCR_CCR 0x98 /* w Clock Control */ +#define SCR_PLL2CR 0x9a /* w PLL2 Control */ +#define SCR_PLL1CR 0x9c /* l PLL1 Control */ +#define SCR_DIARCR 0xa0 /* b Device Internal Active Register Control */ +#define SCR_DBOCR 0xa1 /* b Device Buffer Off Control */ +#define SCR_FER 0xe0 /* b Function Enable */ +#define SCR_MCR 0xe4 /* w Mode Control */ +#define SCR_CONFIG 0xfc /* b Configuration Control */ +#define SCR_DEBUG 0xff /* b Debug */ + +#define SCR_CCR_CK32K BIT(0) +#define SCR_CCR_USBCK BIT(1) +#define SCR_CCR_UNK1 BIT(4) +#define SCR_CCR_MCLK_MASK (7 << 8) +#define SCR_CCR_MCLK_OFF (0 << 8) +#define SCR_CCR_MCLK_12 (1 << 8) +#define SCR_CCR_MCLK_24 (2 << 8) +#define SCR_CCR_MCLK_48 (3 << 8) +#define SCR_CCR_HCLK_MASK (3 << 12) +#define SCR_CCR_HCLK_24 (0 << 12) +#define SCR_CCR_HCLK_48 (1 << 12) + +#define SCR_FER_USBEN BIT(0) /* USB host enable */ +#define SCR_FER_LCDCVEN BIT(1) /* polysilicon TFT enable */ +#define SCR_FER_SLCDEN BIT(2) /* SLCD enable */ + +#define SCR_MCR_RDY_MASK (3 << 0) +#define SCR_MCR_RDY_OPENDRAIN (0 << 0) +#define SCR_MCR_RDY_TRISTATE (1 << 0) +#define SCR_MCR_RDY_PUSHPULL (2 << 0) +#define SCR_MCR_RDY_UNK BIT(2) +#define SCR_MCR_RDY_EN BIT(3) +#define SCR_MCR_INT_MASK (3 << 4) +#define SCR_MCR_INT_OPENDRAIN (0 << 4) +#define SCR_MCR_INT_TRISTATE (1 << 4) +#define SCR_MCR_INT_PUSHPULL (2 << 4) +#define SCR_MCR_INT_UNK BIT(6) +#define SCR_MCR_INT_EN BIT(7) +/* bits 8 - 16 are unknown */ + +#define TC_GPIO_BIT(i) (1 << (i & 0x7)) + +/*--------------------------------------------------------------------------*/ + +struct tc6393xb { + void __iomem *scr; + + struct gpio_chip gpio; + + struct clk *clk; /* 3,6 Mhz */ + + spinlock_t lock; /* protects RMW cycles */ + + struct { + u8 fer; + u16 ccr; + u8 gpi_bcr[3]; + u8 gpo_dsr[3]; + u8 gpo_doecr[3]; + } suspend_state; + + struct resource rscr; + struct resource *iomem; + int irq; + int irq_base; +}; + +enum { + TC6393XB_CELL_NAND, + TC6393XB_CELL_MMC, + TC6393XB_CELL_OHCI, + TC6393XB_CELL_FB, +}; + +/*--------------------------------------------------------------------------*/ + +static int tc6393xb_nand_enable(struct platform_device *nand) +{ + struct platform_device *dev = to_platform_device(nand->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + /* SMD buffer on */ + dev_dbg(&dev->dev, "SMD buffer on\n"); + tmio_iowrite8(0xff, tc6393xb->scr + SCR_GPI_BCR(1)); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static struct resource __devinitdata tc6393xb_nand_resources[] = { + { + .start = 0x1000, + .end = 0x1007, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x0100, + .end = 0x01ff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TC6393_NAND, + .end = IRQ_TC6393_NAND, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource __devinitdata tc6393xb_mmc_resources[] = { + { + .start = 0x800, + .end = 0x9ff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TC6393_MMC, + .end = IRQ_TC6393_MMC, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct resource tc6393xb_ohci_resources[] = { + { + .start = 0x3000, + .end = 0x31ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x0300, + .end = 0x03ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x010000, + .end = 0x017fff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x018000, + .end = 0x01ffff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TC6393_OHCI, + .end = IRQ_TC6393_OHCI, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource __devinitdata tc6393xb_fb_resources[] = { + { + .start = 0x5000, + .end = 0x51ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x0500, + .end = 0x05ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x100000, + .end = 0x1fffff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TC6393_FB, + .end = IRQ_TC6393_FB, + .flags = IORESOURCE_IRQ, + }, +}; + +static int tc6393xb_ohci_enable(struct platform_device *dev) +{ + struct tc6393xb *tc6393xb = dev_get_drvdata(dev->dev.parent); + unsigned long flags; + u16 ccr; + u8 fer; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + ccr = tmio_ioread16(tc6393xb->scr + SCR_CCR); + ccr |= SCR_CCR_USBCK; + tmio_iowrite16(ccr, tc6393xb->scr + SCR_CCR); + + fer = tmio_ioread8(tc6393xb->scr + SCR_FER); + fer |= SCR_FER_USBEN; + tmio_iowrite8(fer, tc6393xb->scr + SCR_FER); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static int tc6393xb_ohci_disable(struct platform_device *dev) +{ + struct tc6393xb *tc6393xb = dev_get_drvdata(dev->dev.parent); + unsigned long flags; + u16 ccr; + u8 fer; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + fer = tmio_ioread8(tc6393xb->scr + SCR_FER); + fer &= ~SCR_FER_USBEN; + tmio_iowrite8(fer, tc6393xb->scr + SCR_FER); + + ccr = tmio_ioread16(tc6393xb->scr + SCR_CCR); + ccr &= ~SCR_CCR_USBCK; + tmio_iowrite16(ccr, tc6393xb->scr + SCR_CCR); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static int tc6393xb_fb_enable(struct platform_device *dev) +{ + struct tc6393xb *tc6393xb = dev_get_drvdata(dev->dev.parent); + unsigned long flags; + u16 ccr; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + ccr = tmio_ioread16(tc6393xb->scr + SCR_CCR); + ccr &= ~SCR_CCR_MCLK_MASK; + ccr |= SCR_CCR_MCLK_48; + tmio_iowrite16(ccr, tc6393xb->scr + SCR_CCR); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static int tc6393xb_fb_disable(struct platform_device *dev) +{ + struct tc6393xb *tc6393xb = dev_get_drvdata(dev->dev.parent); + unsigned long flags; + u16 ccr; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + ccr = tmio_ioread16(tc6393xb->scr + SCR_CCR); + ccr &= ~SCR_CCR_MCLK_MASK; + ccr |= SCR_CCR_MCLK_OFF; + tmio_iowrite16(ccr, tc6393xb->scr + SCR_CCR); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +int tc6393xb_lcd_set_power(struct platform_device *fb, bool on) +{ + struct platform_device *dev = to_platform_device(fb->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + u8 fer; + unsigned long flags; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + fer = ioread8(tc6393xb->scr + SCR_FER); + if (on) + fer |= SCR_FER_SLCDEN; + else + fer &= ~SCR_FER_SLCDEN; + iowrite8(fer, tc6393xb->scr + SCR_FER); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} +EXPORT_SYMBOL(tc6393xb_lcd_set_power); + +int tc6393xb_lcd_mode(struct platform_device *fb, + const struct fb_videomode *mode) { + struct platform_device *dev = to_platform_device(fb->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + iowrite16(mode->pixclock, tc6393xb->scr + SCR_PLL1CR + 0); + iowrite16(mode->pixclock >> 16, tc6393xb->scr + SCR_PLL1CR + 2); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} +EXPORT_SYMBOL(tc6393xb_lcd_mode); + +static int tc6393xb_mmc_enable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + + tmio_core_mmc_enable(tc6393xb->scr + 0x200, 0, + tc6393xb_mmc_resources[0].start & 0xfffe); + + return 0; +} + +static int tc6393xb_mmc_resume(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + + tmio_core_mmc_resume(tc6393xb->scr + 0x200, 0, + tc6393xb_mmc_resources[0].start & 0xfffe); + + return 0; +} + +static void tc6393xb_mmc_pwr(struct platform_device *mmc, int state) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + + tmio_core_mmc_pwr(tc6393xb->scr + 0x200, 0, state); +} + +static void tc6393xb_mmc_clk_div(struct platform_device *mmc, int state) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + + tmio_core_mmc_clk_div(tc6393xb->scr + 0x200, 0, state); +} + +static struct tmio_mmc_data tc6393xb_mmc_data = { + .hclk = 24000000, + .set_pwr = tc6393xb_mmc_pwr, + .set_clk_div = tc6393xb_mmc_clk_div, +}; + +static struct mfd_cell __devinitdata tc6393xb_cells[] = { + [TC6393XB_CELL_NAND] = { + .name = "tmio-nand", + .enable = tc6393xb_nand_enable, + .num_resources = ARRAY_SIZE(tc6393xb_nand_resources), + .resources = tc6393xb_nand_resources, + }, + [TC6393XB_CELL_MMC] = { + .name = "tmio-mmc", + .enable = tc6393xb_mmc_enable, + .resume = tc6393xb_mmc_resume, + .driver_data = &tc6393xb_mmc_data, + .num_resources = ARRAY_SIZE(tc6393xb_mmc_resources), + .resources = tc6393xb_mmc_resources, + }, + [TC6393XB_CELL_OHCI] = { + .name = "tmio-ohci", + .num_resources = ARRAY_SIZE(tc6393xb_ohci_resources), + .resources = tc6393xb_ohci_resources, + .enable = tc6393xb_ohci_enable, + .suspend = tc6393xb_ohci_disable, + .resume = tc6393xb_ohci_enable, + .disable = tc6393xb_ohci_disable, + }, + [TC6393XB_CELL_FB] = { + .name = "tmio-fb", + .num_resources = ARRAY_SIZE(tc6393xb_fb_resources), + .resources = tc6393xb_fb_resources, + .enable = tc6393xb_fb_enable, + .suspend = tc6393xb_fb_disable, + .resume = tc6393xb_fb_enable, + .disable = tc6393xb_fb_disable, + }, +}; + +/*--------------------------------------------------------------------------*/ + +static int tc6393xb_gpio_get(struct gpio_chip *chip, + unsigned offset) +{ + struct tc6393xb *tc6393xb = container_of(chip, struct tc6393xb, gpio); + + /* XXX: does dsr also represent inputs? */ + return tmio_ioread8(tc6393xb->scr + SCR_GPO_DSR(offset / 8)) + & TC_GPIO_BIT(offset); +} + +static void __tc6393xb_gpio_set(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct tc6393xb *tc6393xb = container_of(chip, struct tc6393xb, gpio); + u8 dsr; + + dsr = tmio_ioread8(tc6393xb->scr + SCR_GPO_DSR(offset / 8)); + if (value) + dsr |= TC_GPIO_BIT(offset); + else + dsr &= ~TC_GPIO_BIT(offset); + + tmio_iowrite8(dsr, tc6393xb->scr + SCR_GPO_DSR(offset / 8)); +} + +static void tc6393xb_gpio_set(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct tc6393xb *tc6393xb = container_of(chip, struct tc6393xb, gpio); + unsigned long flags; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + __tc6393xb_gpio_set(chip, offset, value); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); +} + +static int tc6393xb_gpio_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + struct tc6393xb *tc6393xb = container_of(chip, struct tc6393xb, gpio); + unsigned long flags; + u8 doecr; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + doecr = tmio_ioread8(tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); + doecr &= ~TC_GPIO_BIT(offset); + tmio_iowrite8(doecr, tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static int tc6393xb_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct tc6393xb *tc6393xb = container_of(chip, struct tc6393xb, gpio); + unsigned long flags; + u8 doecr; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + __tc6393xb_gpio_set(chip, offset, value); + + doecr = tmio_ioread8(tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); + doecr |= TC_GPIO_BIT(offset); + tmio_iowrite8(doecr, tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static int tc6393xb_register_gpio(struct tc6393xb *tc6393xb, int gpio_base) +{ + tc6393xb->gpio.label = "tc6393xb"; + tc6393xb->gpio.base = gpio_base; + tc6393xb->gpio.ngpio = 16; + tc6393xb->gpio.set = tc6393xb_gpio_set; + tc6393xb->gpio.get = tc6393xb_gpio_get; + tc6393xb->gpio.direction_input = tc6393xb_gpio_direction_input; + tc6393xb->gpio.direction_output = tc6393xb_gpio_direction_output; + + return gpiochip_add(&tc6393xb->gpio); +} + +/*--------------------------------------------------------------------------*/ + +static void +tc6393xb_irq(unsigned int irq, struct irq_desc *desc) +{ + struct tc6393xb *tc6393xb = get_irq_data(irq); + unsigned int isr; + unsigned int i, irq_base; + + irq_base = tc6393xb->irq_base; + + while ((isr = tmio_ioread8(tc6393xb->scr + SCR_ISR) & + ~tmio_ioread8(tc6393xb->scr + SCR_IMR))) + for (i = 0; i < TC6393XB_NR_IRQS; i++) { + if (isr & (1 << i)) + generic_handle_irq(irq_base + i); + } +} + +static void tc6393xb_irq_ack(unsigned int irq) +{ +} + +static void tc6393xb_irq_mask(unsigned int irq) +{ + struct tc6393xb *tc6393xb = get_irq_chip_data(irq); + unsigned long flags; + u8 imr; + + spin_lock_irqsave(&tc6393xb->lock, flags); + imr = tmio_ioread8(tc6393xb->scr + SCR_IMR); + imr |= 1 << (irq - tc6393xb->irq_base); + tmio_iowrite8(imr, tc6393xb->scr + SCR_IMR); + spin_unlock_irqrestore(&tc6393xb->lock, flags); +} + +static void tc6393xb_irq_unmask(unsigned int irq) +{ + struct tc6393xb *tc6393xb = get_irq_chip_data(irq); + unsigned long flags; + u8 imr; + + spin_lock_irqsave(&tc6393xb->lock, flags); + imr = tmio_ioread8(tc6393xb->scr + SCR_IMR); + imr &= ~(1 << (irq - tc6393xb->irq_base)); + tmio_iowrite8(imr, tc6393xb->scr + SCR_IMR); + spin_unlock_irqrestore(&tc6393xb->lock, flags); +} + +static struct irq_chip tc6393xb_chip = { + .name = "tc6393xb", + .ack = tc6393xb_irq_ack, + .mask = tc6393xb_irq_mask, + .unmask = tc6393xb_irq_unmask, +}; + +static void tc6393xb_attach_irq(struct platform_device *dev) +{ + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + unsigned int irq, irq_base; + + irq_base = tc6393xb->irq_base; + + for (irq = irq_base; irq < irq_base + TC6393XB_NR_IRQS; irq++) { + set_irq_chip(irq, &tc6393xb_chip); + set_irq_chip_data(irq, tc6393xb); + set_irq_handler(irq, handle_edge_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + } + + set_irq_type(tc6393xb->irq, IRQ_TYPE_EDGE_FALLING); + set_irq_data(tc6393xb->irq, tc6393xb); + set_irq_chained_handler(tc6393xb->irq, tc6393xb_irq); +} + +static void tc6393xb_detach_irq(struct platform_device *dev) +{ + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + unsigned int irq, irq_base; + + set_irq_chained_handler(tc6393xb->irq, NULL); + set_irq_data(tc6393xb->irq, NULL); + + irq_base = tc6393xb->irq_base; + + for (irq = irq_base; irq < irq_base + TC6393XB_NR_IRQS; irq++) { + set_irq_flags(irq, 0); + set_irq_chip(irq, NULL); + set_irq_chip_data(irq, NULL); + } +} + +/*--------------------------------------------------------------------------*/ + +static int __devinit tc6393xb_probe(struct platform_device *dev) +{ + struct tc6393xb_platform_data *tcpd = dev->dev.platform_data; + struct tc6393xb *tc6393xb; + struct resource *iomem, *rscr; + int ret, temp; + + iomem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!iomem) + return -EINVAL; + + tc6393xb = kzalloc(sizeof *tc6393xb, GFP_KERNEL); + if (!tc6393xb) { + ret = -ENOMEM; + goto err_kzalloc; + } + + spin_lock_init(&tc6393xb->lock); + + platform_set_drvdata(dev, tc6393xb); + + ret = platform_get_irq(dev, 0); + if (ret >= 0) + tc6393xb->irq = ret; + else + goto err_noirq; + + tc6393xb->iomem = iomem; + tc6393xb->irq_base = tcpd->irq_base; + + tc6393xb->clk = clk_get(&dev->dev, "CLK_CK3P6MI"); + if (IS_ERR(tc6393xb->clk)) { + ret = PTR_ERR(tc6393xb->clk); + goto err_clk_get; + } + + rscr = &tc6393xb->rscr; + rscr->name = "tc6393xb-core"; + rscr->start = iomem->start; + rscr->end = iomem->start + 0xff; + rscr->flags = IORESOURCE_MEM; + + ret = request_resource(iomem, rscr); + if (ret) + goto err_request_scr; + + tc6393xb->scr = ioremap(rscr->start, rscr->end - rscr->start + 1); + if (!tc6393xb->scr) { + ret = -ENOMEM; + goto err_ioremap; + } + + ret = clk_enable(tc6393xb->clk); + if (ret) + goto err_clk_enable; + + ret = tcpd->enable(dev); + if (ret) + goto err_enable; + + iowrite8(0, tc6393xb->scr + SCR_FER); + iowrite16(tcpd->scr_pll2cr, tc6393xb->scr + SCR_PLL2CR); + iowrite16(SCR_CCR_UNK1 | SCR_CCR_HCLK_48, + tc6393xb->scr + SCR_CCR); + iowrite16(SCR_MCR_RDY_OPENDRAIN | SCR_MCR_RDY_UNK | SCR_MCR_RDY_EN | + SCR_MCR_INT_OPENDRAIN | SCR_MCR_INT_UNK | SCR_MCR_INT_EN | + BIT(15), tc6393xb->scr + SCR_MCR); + iowrite16(tcpd->scr_gper, tc6393xb->scr + SCR_GPER); + iowrite8(0, tc6393xb->scr + SCR_IRR); + iowrite8(0xbf, tc6393xb->scr + SCR_IMR); + + printk(KERN_INFO "Toshiba tc6393xb revision %d at 0x%08lx, irq %d\n", + tmio_ioread8(tc6393xb->scr + SCR_REVID), + (unsigned long) iomem->start, tc6393xb->irq); + + tc6393xb->gpio.base = -1; + + if (tcpd->gpio_base >= 0) { + ret = tc6393xb_register_gpio(tc6393xb, tcpd->gpio_base); + if (ret) + goto err_gpio_add; + } + + tc6393xb_attach_irq(dev); + + if (tcpd->setup) { + ret = tcpd->setup(dev); + if (ret) + goto err_setup; + } + + tc6393xb_cells[TC6393XB_CELL_NAND].driver_data = tcpd->nand_data; + tc6393xb_cells[TC6393XB_CELL_NAND].platform_data = + &tc6393xb_cells[TC6393XB_CELL_NAND]; + tc6393xb_cells[TC6393XB_CELL_NAND].data_size = + sizeof(tc6393xb_cells[TC6393XB_CELL_NAND]); + + tc6393xb_cells[TC6393XB_CELL_MMC].platform_data = + &tc6393xb_cells[TC6393XB_CELL_MMC]; + tc6393xb_cells[TC6393XB_CELL_MMC].data_size = + sizeof(tc6393xb_cells[TC6393XB_CELL_MMC]); + + tc6393xb_cells[TC6393XB_CELL_OHCI].platform_data = + &tc6393xb_cells[TC6393XB_CELL_OHCI]; + tc6393xb_cells[TC6393XB_CELL_OHCI].data_size = + sizeof(tc6393xb_cells[TC6393XB_CELL_OHCI]); + + tc6393xb_cells[TC6393XB_CELL_FB].driver_data = tcpd->fb_data; + tc6393xb_cells[TC6393XB_CELL_FB].platform_data = + &tc6393xb_cells[TC6393XB_CELL_FB]; + tc6393xb_cells[TC6393XB_CELL_FB].data_size = + sizeof(tc6393xb_cells[TC6393XB_CELL_FB]); + + ret = mfd_add_devices(&dev->dev, dev->id, + tc6393xb_cells, ARRAY_SIZE(tc6393xb_cells), + iomem, tcpd->irq_base); + + if (!ret) + return 0; + + if (tcpd->teardown) + tcpd->teardown(dev); + +err_setup: + tc6393xb_detach_irq(dev); + +err_gpio_add: + if (tc6393xb->gpio.base != -1) + temp = gpiochip_remove(&tc6393xb->gpio); + tcpd->disable(dev); +err_clk_enable: + clk_disable(tc6393xb->clk); +err_enable: + iounmap(tc6393xb->scr); +err_ioremap: + release_resource(&tc6393xb->rscr); +err_request_scr: + clk_put(tc6393xb->clk); +err_noirq: +err_clk_get: + kfree(tc6393xb); +err_kzalloc: + return ret; +} + +static int __devexit tc6393xb_remove(struct platform_device *dev) +{ + struct tc6393xb_platform_data *tcpd = dev->dev.platform_data; + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + int ret; + + mfd_remove_devices(&dev->dev); + + if (tcpd->teardown) + tcpd->teardown(dev); + + tc6393xb_detach_irq(dev); + + if (tc6393xb->gpio.base != -1) { + ret = gpiochip_remove(&tc6393xb->gpio); + if (ret) { + dev_err(&dev->dev, "Can't remove gpio chip: %d\n", ret); + return ret; + } + } + + ret = tcpd->disable(dev); + clk_disable(tc6393xb->clk); + iounmap(tc6393xb->scr); + release_resource(&tc6393xb->rscr); + platform_set_drvdata(dev, NULL); + clk_put(tc6393xb->clk); + kfree(tc6393xb); + + return ret; +} + +#ifdef CONFIG_PM +static int tc6393xb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct tc6393xb_platform_data *tcpd = dev->dev.platform_data; + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + int i, ret; + + tc6393xb->suspend_state.ccr = ioread16(tc6393xb->scr + SCR_CCR); + tc6393xb->suspend_state.fer = ioread8(tc6393xb->scr + SCR_FER); + + for (i = 0; i < 3; i++) { + tc6393xb->suspend_state.gpo_dsr[i] = + ioread8(tc6393xb->scr + SCR_GPO_DSR(i)); + tc6393xb->suspend_state.gpo_doecr[i] = + ioread8(tc6393xb->scr + SCR_GPO_DOECR(i)); + tc6393xb->suspend_state.gpi_bcr[i] = + ioread8(tc6393xb->scr + SCR_GPI_BCR(i)); + } + ret = tcpd->suspend(dev); + clk_disable(tc6393xb->clk); + + return ret; +} + +static int tc6393xb_resume(struct platform_device *dev) +{ + struct tc6393xb_platform_data *tcpd = dev->dev.platform_data; + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + int ret; + int i; + + clk_enable(tc6393xb->clk); + + ret = tcpd->resume(dev); + if (ret) + return ret; + + if (!tcpd->resume_restore) + return 0; + + iowrite8(tc6393xb->suspend_state.fer, tc6393xb->scr + SCR_FER); + iowrite16(tcpd->scr_pll2cr, tc6393xb->scr + SCR_PLL2CR); + iowrite16(tc6393xb->suspend_state.ccr, tc6393xb->scr + SCR_CCR); + iowrite16(SCR_MCR_RDY_OPENDRAIN | SCR_MCR_RDY_UNK | SCR_MCR_RDY_EN | + SCR_MCR_INT_OPENDRAIN | SCR_MCR_INT_UNK | SCR_MCR_INT_EN | + BIT(15), tc6393xb->scr + SCR_MCR); + iowrite16(tcpd->scr_gper, tc6393xb->scr + SCR_GPER); + iowrite8(0, tc6393xb->scr + SCR_IRR); + iowrite8(0xbf, tc6393xb->scr + SCR_IMR); + + for (i = 0; i < 3; i++) { + iowrite8(tc6393xb->suspend_state.gpo_dsr[i], + tc6393xb->scr + SCR_GPO_DSR(i)); + iowrite8(tc6393xb->suspend_state.gpo_doecr[i], + tc6393xb->scr + SCR_GPO_DOECR(i)); + iowrite8(tc6393xb->suspend_state.gpi_bcr[i], + tc6393xb->scr + SCR_GPI_BCR(i)); + } + + return 0; +} +#else +#define tc6393xb_suspend NULL +#define tc6393xb_resume NULL +#endif + +static struct platform_driver tc6393xb_driver = { + .probe = tc6393xb_probe, + .remove = __devexit_p(tc6393xb_remove), + .suspend = tc6393xb_suspend, + .resume = tc6393xb_resume, + + .driver = { + .name = "tc6393xb", + .owner = THIS_MODULE, + }, +}; + +static int __init tc6393xb_init(void) +{ + return platform_driver_register(&tc6393xb_driver); +} + +static void __exit tc6393xb_exit(void) +{ + platform_driver_unregister(&tc6393xb_driver); +} + +subsys_initcall(tc6393xb_init); +module_exit(tc6393xb_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov and Dirk Opfer"); +MODULE_DESCRIPTION("tc6393xb Toshiba Mobile IO Controller"); +MODULE_ALIAS("platform:tc6393xb"); + diff --git a/drivers/mfd/tmio_core.c b/drivers/mfd/tmio_core.c new file mode 100644 index 00000000000..eddc19ae464 --- /dev/null +++ b/drivers/mfd/tmio_core.c @@ -0,0 +1,52 @@ +/* + * Copyright(c) 2009 Ian Molton <spyro@f2s.com> + * + * 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 <linux/mfd/tmio.h> + +int tmio_core_mmc_enable(void __iomem *cnf, int shift, unsigned long base) +{ + /* Enable the MMC/SD Control registers */ + sd_config_write16(cnf, shift, CNF_CMD, SDCREN); + sd_config_write32(cnf, shift, CNF_CTL_BASE, base & 0xfffe); + + /* Disable SD power during suspend */ + sd_config_write8(cnf, shift, CNF_PWR_CTL_3, 0x01); + + /* The below is required but why? FIXME */ + sd_config_write8(cnf, shift, CNF_STOP_CLK_CTL, 0x1f); + + /* Power down SD bus */ + sd_config_write8(cnf, shift, CNF_PWR_CTL_2, 0x00); + + return 0; +} +EXPORT_SYMBOL(tmio_core_mmc_enable); + +int tmio_core_mmc_resume(void __iomem *cnf, int shift, unsigned long base) +{ + + /* Enable the MMC/SD Control registers */ + sd_config_write16(cnf, shift, CNF_CMD, SDCREN); + sd_config_write32(cnf, shift, CNF_CTL_BASE, base & 0xfffe); + + return 0; +} +EXPORT_SYMBOL(tmio_core_mmc_resume); + +void tmio_core_mmc_pwr(void __iomem *cnf, int shift, int state) +{ + sd_config_write8(cnf, shift, CNF_PWR_CTL_2, state ? 0x02 : 0x00); +} +EXPORT_SYMBOL(tmio_core_mmc_pwr); + +void tmio_core_mmc_clk_div(void __iomem *cnf, int shift, int state) +{ + sd_config_write8(cnf, shift, CNF_SD_CLK_MODE, state ? 1 : 0); +} +EXPORT_SYMBOL(tmio_core_mmc_clk_div); + diff --git a/drivers/mfd/tps65010.c b/drivers/mfd/tps65010.c new file mode 100644 index 00000000000..e5955306c2f --- /dev/null +++ b/drivers/mfd/tps65010.c @@ -0,0 +1,1100 @@ +/* + * tps65010 - driver for tps6501x power management chips + * + * Copyright (C) 2004 Texas Instruments + * Copyright (C) 2004-2005 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> + +#include <linux/i2c/tps65010.h> + +#include <asm/gpio.h> + + +/*-------------------------------------------------------------------------*/ + +#define DRIVER_VERSION "2 May 2005" +#define DRIVER_NAME (tps65010_driver.driver.name) + +MODULE_DESCRIPTION("TPS6501x Power Management Driver"); +MODULE_LICENSE("GPL"); + +static struct i2c_driver tps65010_driver; + +/*-------------------------------------------------------------------------*/ + +/* This driver handles a family of multipurpose chips, which incorporate + * voltage regulators, lithium ion/polymer battery charging, GPIOs, LEDs, + * and other features often needed in portable devices like cell phones + * or digital cameras. + * + * The tps65011 and tps65013 have different voltage settings compared + * to tps65010 and tps65012. The tps65013 has a NO_CHG status/irq. + * All except tps65010 have "wait" mode, possibly defaulted so that + * battery-insert != device-on. + * + * We could distinguish between some models by checking VDCDC1.UVLO or + * other registers, unless they've been changed already after powerup + * as part of board setup by a bootloader. + */ +enum tps_model { + TPS65010, + TPS65011, + TPS65012, + TPS65013, +}; + +struct tps65010 { + struct i2c_client *client; + struct mutex lock; + struct delayed_work work; + struct dentry *file; + unsigned charging:1; + unsigned por:1; + unsigned model:8; + u16 vbus; + unsigned long flags; +#define FLAG_VBUS_CHANGED 0 +#define FLAG_IRQ_ENABLE 1 + + /* copies of last register state */ + u8 chgstatus, regstatus, chgconf; + u8 nmask1, nmask2; + + u8 outmask; + struct gpio_chip chip; + struct platform_device *leds; +}; + +#define POWER_POLL_DELAY msecs_to_jiffies(5000) + +/*-------------------------------------------------------------------------*/ + +#if defined(DEBUG) || defined(CONFIG_DEBUG_FS) + +static void dbg_chgstat(char *buf, size_t len, u8 chgstatus) +{ + snprintf(buf, len, "%02x%s%s%s%s%s%s%s%s\n", + chgstatus, + (chgstatus & TPS_CHG_USB) ? " USB" : "", + (chgstatus & TPS_CHG_AC) ? " AC" : "", + (chgstatus & TPS_CHG_THERM) ? " therm" : "", + (chgstatus & TPS_CHG_TERM) ? " done" : + ((chgstatus & (TPS_CHG_USB|TPS_CHG_AC)) + ? " (charging)" : ""), + (chgstatus & TPS_CHG_TAPER_TMO) ? " taper_tmo" : "", + (chgstatus & TPS_CHG_CHG_TMO) ? " charge_tmo" : "", + (chgstatus & TPS_CHG_PRECHG_TMO) ? " prechg_tmo" : "", + (chgstatus & TPS_CHG_TEMP_ERR) ? " temp_err" : ""); +} + +static void dbg_regstat(char *buf, size_t len, u8 regstatus) +{ + snprintf(buf, len, "%02x %s%s%s%s%s%s%s%s\n", + regstatus, + (regstatus & TPS_REG_ONOFF) ? "off" : "(on)", + (regstatus & TPS_REG_COVER) ? " uncover" : "", + (regstatus & TPS_REG_UVLO) ? " UVLO" : "", + (regstatus & TPS_REG_NO_CHG) ? " NO_CHG" : "", + (regstatus & TPS_REG_PG_LD02) ? " ld02_bad" : "", + (regstatus & TPS_REG_PG_LD01) ? " ld01_bad" : "", + (regstatus & TPS_REG_PG_MAIN) ? " main_bad" : "", + (regstatus & TPS_REG_PG_CORE) ? " core_bad" : ""); +} + +static void dbg_chgconf(int por, char *buf, size_t len, u8 chgconfig) +{ + const char *hibit; + + if (por) + hibit = (chgconfig & TPS_CHARGE_POR) + ? "POR=69ms" : "POR=1sec"; + else + hibit = (chgconfig & TPS65013_AUA) ? "AUA" : ""; + + snprintf(buf, len, "%02x %s%s%s AC=%d%% USB=%dmA %sCharge\n", + chgconfig, hibit, + (chgconfig & TPS_CHARGE_RESET) ? " reset" : "", + (chgconfig & TPS_CHARGE_FAST) ? " fast" : "", + ({int p; switch ((chgconfig >> 3) & 3) { + case 3: p = 100; break; + case 2: p = 75; break; + case 1: p = 50; break; + default: p = 25; break; + }; p; }), + (chgconfig & TPS_VBUS_CHARGING) + ? ((chgconfig & TPS_VBUS_500MA) ? 500 : 100) + : 0, + (chgconfig & TPS_CHARGE_ENABLE) ? "" : "No"); +} + +#endif + +#ifdef DEBUG + +static void show_chgstatus(const char *label, u8 chgstatus) +{ + char buf [100]; + + dbg_chgstat(buf, sizeof buf, chgstatus); + pr_debug("%s: %s %s", DRIVER_NAME, label, buf); +} + +static void show_regstatus(const char *label, u8 regstatus) +{ + char buf [100]; + + dbg_regstat(buf, sizeof buf, regstatus); + pr_debug("%s: %s %s", DRIVER_NAME, label, buf); +} + +static void show_chgconfig(int por, const char *label, u8 chgconfig) +{ + char buf [100]; + + dbg_chgconf(por, buf, sizeof buf, chgconfig); + pr_debug("%s: %s %s", DRIVER_NAME, label, buf); +} + +#else + +static inline void show_chgstatus(const char *label, u8 chgstatus) { } +static inline void show_regstatus(const char *label, u8 chgstatus) { } +static inline void show_chgconfig(int por, const char *label, u8 chgconfig) { } + +#endif + +#ifdef CONFIG_DEBUG_FS + +static int dbg_show(struct seq_file *s, void *_) +{ + struct tps65010 *tps = s->private; + u8 value, v2; + unsigned i; + char buf[100]; + const char *chip; + + switch (tps->model) { + case TPS65010: chip = "tps65010"; break; + case TPS65011: chip = "tps65011"; break; + case TPS65012: chip = "tps65012"; break; + case TPS65013: chip = "tps65013"; break; + default: chip = NULL; break; + } + seq_printf(s, "driver %s\nversion %s\nchip %s\n\n", + DRIVER_NAME, DRIVER_VERSION, chip); + + mutex_lock(&tps->lock); + + /* FIXME how can we tell whether a battery is present? + * likely involves a charge gauging chip (like BQ26501). + */ + + seq_printf(s, "%scharging\n\n", tps->charging ? "" : "(not) "); + + + /* registers for monitoring battery charging and status; note + * that reading chgstat and regstat may ack IRQs... + */ + value = i2c_smbus_read_byte_data(tps->client, TPS_CHGCONFIG); + dbg_chgconf(tps->por, buf, sizeof buf, value); + seq_printf(s, "chgconfig %s", buf); + + value = i2c_smbus_read_byte_data(tps->client, TPS_CHGSTATUS); + dbg_chgstat(buf, sizeof buf, value); + seq_printf(s, "chgstat %s", buf); + value = i2c_smbus_read_byte_data(tps->client, TPS_MASK1); + dbg_chgstat(buf, sizeof buf, value); + seq_printf(s, "mask1 %s", buf); + /* ignore ackint1 */ + + value = i2c_smbus_read_byte_data(tps->client, TPS_REGSTATUS); + dbg_regstat(buf, sizeof buf, value); + seq_printf(s, "regstat %s", buf); + value = i2c_smbus_read_byte_data(tps->client, TPS_MASK2); + dbg_regstat(buf, sizeof buf, value); + seq_printf(s, "mask2 %s\n", buf); + /* ignore ackint2 */ + + (void) schedule_delayed_work(&tps->work, POWER_POLL_DELAY); + + + /* VMAIN voltage, enable lowpower, etc */ + value = i2c_smbus_read_byte_data(tps->client, TPS_VDCDC1); + seq_printf(s, "vdcdc1 %02x\n", value); + + /* VCORE voltage, vibrator on/off */ + value = i2c_smbus_read_byte_data(tps->client, TPS_VDCDC2); + seq_printf(s, "vdcdc2 %02x\n", value); + + /* both LD0s, and their lowpower behavior */ + value = i2c_smbus_read_byte_data(tps->client, TPS_VREGS1); + seq_printf(s, "vregs1 %02x\n\n", value); + + + /* LEDs and GPIOs */ + value = i2c_smbus_read_byte_data(tps->client, TPS_LED1_ON); + v2 = i2c_smbus_read_byte_data(tps->client, TPS_LED1_PER); + seq_printf(s, "led1 %s, on=%02x, per=%02x, %d/%d msec\n", + (value & 0x80) + ? ((v2 & 0x80) ? "on" : "off") + : ((v2 & 0x80) ? "blink" : "(nPG)"), + value, v2, + (value & 0x7f) * 10, (v2 & 0x7f) * 100); + + value = i2c_smbus_read_byte_data(tps->client, TPS_LED2_ON); + v2 = i2c_smbus_read_byte_data(tps->client, TPS_LED2_PER); + seq_printf(s, "led2 %s, on=%02x, per=%02x, %d/%d msec\n", + (value & 0x80) + ? ((v2 & 0x80) ? "on" : "off") + : ((v2 & 0x80) ? "blink" : "off"), + value, v2, + (value & 0x7f) * 10, (v2 & 0x7f) * 100); + + value = i2c_smbus_read_byte_data(tps->client, TPS_DEFGPIO); + v2 = i2c_smbus_read_byte_data(tps->client, TPS_MASK3); + seq_printf(s, "defgpio %02x mask3 %02x\n", value, v2); + + for (i = 0; i < 4; i++) { + if (value & (1 << (4 + i))) + seq_printf(s, " gpio%d-out %s\n", i + 1, + (value & (1 << i)) ? "low" : "hi "); + else + seq_printf(s, " gpio%d-in %s %s %s\n", i + 1, + (value & (1 << i)) ? "hi " : "low", + (v2 & (1 << i)) ? "no-irq" : "irq", + (v2 & (1 << (4 + i))) ? "rising" : "falling"); + } + + mutex_unlock(&tps->lock); + return 0; +} + +static int dbg_tps_open(struct inode *inode, struct file *file) +{ + return single_open(file, dbg_show, inode->i_private); +} + +static const struct file_operations debug_fops = { + .open = dbg_tps_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define DEBUG_FOPS &debug_fops + +#else +#define DEBUG_FOPS NULL +#endif + +/*-------------------------------------------------------------------------*/ + +/* handle IRQS in a task context, so we can use I2C calls */ +static void tps65010_interrupt(struct tps65010 *tps) +{ + u8 tmp = 0, mask, poll; + + /* IRQs won't trigger for certain events, but we can get + * others by polling (normally, with external power applied). + */ + poll = 0; + + /* regstatus irqs */ + if (tps->nmask2) { + tmp = i2c_smbus_read_byte_data(tps->client, TPS_REGSTATUS); + mask = tmp ^ tps->regstatus; + tps->regstatus = tmp; + mask &= tps->nmask2; + } else + mask = 0; + if (mask) { + tps->regstatus = tmp; + /* may need to shut something down ... */ + + /* "off" usually means deep sleep */ + if (tmp & TPS_REG_ONOFF) { + pr_info("%s: power off button\n", DRIVER_NAME); +#if 0 + /* REVISIT: this might need its own workqueue + * plus tweaks including deadlock avoidance ... + * also needs to get error handling and probably + * an #ifdef CONFIG_HIBERNATION + */ + hibernate(); +#endif + poll = 1; + } + } + + /* chgstatus irqs */ + if (tps->nmask1) { + tmp = i2c_smbus_read_byte_data(tps->client, TPS_CHGSTATUS); + mask = tmp ^ tps->chgstatus; + tps->chgstatus = tmp; + mask &= tps->nmask1; + } else + mask = 0; + if (mask) { + unsigned charging = 0; + + show_chgstatus("chg/irq", tmp); + if (tmp & (TPS_CHG_USB|TPS_CHG_AC)) + show_chgconfig(tps->por, "conf", tps->chgconf); + + /* Unless it was turned off or disabled, we charge any + * battery whenever there's power available for it + * and the charger hasn't been disabled. + */ + if (!(tps->chgstatus & ~(TPS_CHG_USB|TPS_CHG_AC)) + && (tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC)) + && (tps->chgconf & TPS_CHARGE_ENABLE) + ) { + if (tps->chgstatus & TPS_CHG_USB) { + /* VBUS options are readonly until reconnect */ + if (mask & TPS_CHG_USB) + set_bit(FLAG_VBUS_CHANGED, &tps->flags); + charging = 1; + } else if (tps->chgstatus & TPS_CHG_AC) + charging = 1; + } + if (charging != tps->charging) { + tps->charging = charging; + pr_info("%s: battery %scharging\n", + DRIVER_NAME, charging ? "" : + ((tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC)) + ? "NOT " : "dis")); + } + } + + /* always poll to detect (a) power removal, without tps65013 + * NO_CHG IRQ; or (b) restart of charging after stop. + */ + if ((tps->model != TPS65013 || !tps->charging) + && (tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC))) + poll = 1; + if (poll) + (void) schedule_delayed_work(&tps->work, POWER_POLL_DELAY); + + /* also potentially gpio-in rise or fall */ +} + +/* handle IRQs and polling using keventd for now */ +static void tps65010_work(struct work_struct *work) +{ + struct tps65010 *tps; + + tps = container_of(work, struct tps65010, work.work); + mutex_lock(&tps->lock); + + tps65010_interrupt(tps); + + if (test_and_clear_bit(FLAG_VBUS_CHANGED, &tps->flags)) { + int status; + u8 chgconfig, tmp; + + chgconfig = i2c_smbus_read_byte_data(tps->client, + TPS_CHGCONFIG); + chgconfig &= ~(TPS_VBUS_500MA | TPS_VBUS_CHARGING); + if (tps->vbus == 500) + chgconfig |= TPS_VBUS_500MA | TPS_VBUS_CHARGING; + else if (tps->vbus >= 100) + chgconfig |= TPS_VBUS_CHARGING; + + status = i2c_smbus_write_byte_data(tps->client, + TPS_CHGCONFIG, chgconfig); + + /* vbus update fails unless VBUS is connected! */ + tmp = i2c_smbus_read_byte_data(tps->client, TPS_CHGCONFIG); + tps->chgconf = tmp; + show_chgconfig(tps->por, "update vbus", tmp); + } + + if (test_and_clear_bit(FLAG_IRQ_ENABLE, &tps->flags)) + enable_irq(tps->client->irq); + + mutex_unlock(&tps->lock); +} + +static irqreturn_t tps65010_irq(int irq, void *_tps) +{ + struct tps65010 *tps = _tps; + + disable_irq_nosync(irq); + set_bit(FLAG_IRQ_ENABLE, &tps->flags); + (void) schedule_work(&tps->work.work); + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +/* offsets 0..3 == GPIO1..GPIO4 + * offsets 4..5 == LED1/nPG, LED2 (we set one of the non-BLINK modes) + * offset 6 == vibrator motor driver + */ +static void +tps65010_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + if (offset < 4) + tps65010_set_gpio_out_value(offset + 1, value); + else if (offset < 6) + tps65010_set_led(offset - 3, value ? ON : OFF); + else + tps65010_set_vib(value); +} + +static int +tps65010_output(struct gpio_chip *chip, unsigned offset, int value) +{ + /* GPIOs may be input-only */ + if (offset < 4) { + struct tps65010 *tps; + + tps = container_of(chip, struct tps65010, chip); + if (!(tps->outmask & (1 << offset))) + return -EINVAL; + tps65010_set_gpio_out_value(offset + 1, value); + } else if (offset < 6) + tps65010_set_led(offset - 3, value ? ON : OFF); + else + tps65010_set_vib(value); + + return 0; +} + +static int tps65010_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + int value; + struct tps65010 *tps; + + tps = container_of(chip, struct tps65010, chip); + + if (offset < 4) { + value = i2c_smbus_read_byte_data(tps->client, TPS_DEFGPIO); + if (value < 0) + return 0; + if (value & (1 << (offset + 4))) /* output */ + return !(value & (1 << offset)); + else /* input */ + return (value & (1 << offset)); + } + + /* REVISIT we *could* report LED1/nPG and LED2 state ... */ + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static struct tps65010 *the_tps; + +static int __exit tps65010_remove(struct i2c_client *client) +{ + struct tps65010 *tps = i2c_get_clientdata(client); + struct tps65010_board *board = client->dev.platform_data; + + if (board && board->teardown) { + int status = board->teardown(client, board->context); + if (status < 0) + dev_dbg(&client->dev, "board %s %s err %d\n", + "teardown", client->name, status); + } + if (client->irq > 0) + free_irq(client->irq, tps); + cancel_delayed_work(&tps->work); + flush_scheduled_work(); + debugfs_remove(tps->file); + kfree(tps); + i2c_set_clientdata(client, NULL); + the_tps = NULL; + return 0; +} + +static int tps65010_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tps65010 *tps; + int status; + struct tps65010_board *board = client->dev.platform_data; + + if (the_tps) { + dev_dbg(&client->dev, "only one tps6501x chip allowed\n"); + return -ENODEV; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EINVAL; + + tps = kzalloc(sizeof *tps, GFP_KERNEL); + if (!tps) + return -ENOMEM; + + mutex_init(&tps->lock); + INIT_DELAYED_WORK(&tps->work, tps65010_work); + tps->client = client; + tps->model = id->driver_data; + + /* the IRQ is active low, but many gpio lines can't support that + * so this driver uses falling-edge triggers instead. + */ + if (client->irq > 0) { + status = request_irq(client->irq, tps65010_irq, + IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_FALLING, + DRIVER_NAME, tps); + if (status < 0) { + dev_dbg(&client->dev, "can't get IRQ %d, err %d\n", + client->irq, status); + goto fail1; + } + /* annoying race here, ideally we'd have an option + * to claim the irq now and enable it later. + * FIXME genirq IRQF_NOAUTOEN now solves that ... + */ + disable_irq(client->irq); + set_bit(FLAG_IRQ_ENABLE, &tps->flags); + } else + dev_warn(&client->dev, "IRQ not configured!\n"); + + + switch (tps->model) { + case TPS65010: + case TPS65012: + tps->por = 1; + break; + /* else CHGCONFIG.POR is replaced by AUA, enabling a WAIT mode */ + } + tps->chgconf = i2c_smbus_read_byte_data(client, TPS_CHGCONFIG); + show_chgconfig(tps->por, "conf/init", tps->chgconf); + + show_chgstatus("chg/init", + i2c_smbus_read_byte_data(client, TPS_CHGSTATUS)); + show_regstatus("reg/init", + i2c_smbus_read_byte_data(client, TPS_REGSTATUS)); + + pr_debug("%s: vdcdc1 0x%02x, vdcdc2 %02x, vregs1 %02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(client, TPS_VDCDC1), + i2c_smbus_read_byte_data(client, TPS_VDCDC2), + i2c_smbus_read_byte_data(client, TPS_VREGS1)); + pr_debug("%s: defgpio 0x%02x, mask3 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(client, TPS_DEFGPIO), + i2c_smbus_read_byte_data(client, TPS_MASK3)); + + i2c_set_clientdata(client, tps); + the_tps = tps; + +#if defined(CONFIG_USB_GADGET) && !defined(CONFIG_USB_OTG) + /* USB hosts can't draw VBUS. OTG devices could, later + * when OTG infrastructure enables it. USB peripherals + * could be relying on VBUS while booting, though. + */ + tps->vbus = 100; +#endif + + /* unmask the "interesting" irqs, then poll once to + * kickstart monitoring, initialize shadowed status + * registers, and maybe disable VBUS draw. + */ + tps->nmask1 = ~0; + (void) i2c_smbus_write_byte_data(client, TPS_MASK1, ~tps->nmask1); + + tps->nmask2 = TPS_REG_ONOFF; + if (tps->model == TPS65013) + tps->nmask2 |= TPS_REG_NO_CHG; + (void) i2c_smbus_write_byte_data(client, TPS_MASK2, ~tps->nmask2); + + (void) i2c_smbus_write_byte_data(client, TPS_MASK3, 0x0f + | i2c_smbus_read_byte_data(client, TPS_MASK3)); + + tps65010_work(&tps->work.work); + + tps->file = debugfs_create_file(DRIVER_NAME, S_IRUGO, NULL, + tps, DEBUG_FOPS); + + /* optionally register GPIOs */ + if (board && board->base != 0) { + tps->outmask = board->outmask; + + tps->chip.label = client->name; + tps->chip.dev = &client->dev; + tps->chip.owner = THIS_MODULE; + + tps->chip.set = tps65010_gpio_set; + tps->chip.direction_output = tps65010_output; + + /* NOTE: only partial support for inputs; nyet IRQs */ + tps->chip.get = tps65010_gpio_get; + + tps->chip.base = board->base; + tps->chip.ngpio = 7; + tps->chip.can_sleep = 1; + + status = gpiochip_add(&tps->chip); + if (status < 0) + dev_err(&client->dev, "can't add gpiochip, err %d\n", + status); + else if (board->setup) { + status = board->setup(client, board->context); + if (status < 0) { + dev_dbg(&client->dev, + "board %s %s err %d\n", + "setup", client->name, status); + status = 0; + } + } + } + + return 0; +fail1: + kfree(tps); + return status; +} + +static const struct i2c_device_id tps65010_id[] = { + { "tps65010", TPS65010 }, + { "tps65011", TPS65011 }, + { "tps65012", TPS65012 }, + { "tps65013", TPS65013 }, + { "tps65014", TPS65011 }, /* tps65011 charging at 6.5V max */ + { } +}; +MODULE_DEVICE_TABLE(i2c, tps65010_id); + +static struct i2c_driver tps65010_driver = { + .driver = { + .name = "tps65010", + }, + .probe = tps65010_probe, + .remove = __exit_p(tps65010_remove), + .id_table = tps65010_id, +}; + +/*-------------------------------------------------------------------------*/ + +/* Draw from VBUS: + * 0 mA -- DON'T DRAW (might supply power instead) + * 100 mA -- usb unit load (slowest charge rate) + * 500 mA -- usb high power (fast battery charge) + */ +int tps65010_set_vbus_draw(unsigned mA) +{ + unsigned long flags; + + if (!the_tps) + return -ENODEV; + + /* assumes non-SMP */ + local_irq_save(flags); + if (mA >= 500) + mA = 500; + else if (mA >= 100) + mA = 100; + else + mA = 0; + the_tps->vbus = mA; + if ((the_tps->chgstatus & TPS_CHG_USB) + && test_and_set_bit( + FLAG_VBUS_CHANGED, &the_tps->flags)) { + /* gadget drivers call this in_irq() */ + (void) schedule_work(&the_tps->work.work); + } + local_irq_restore(flags); + + return 0; +} +EXPORT_SYMBOL(tps65010_set_vbus_draw); + +/*-------------------------------------------------------------------------*/ +/* tps65010_set_gpio_out_value parameter: + * gpio: GPIO1, GPIO2, GPIO3 or GPIO4 + * value: LOW or HIGH + */ +int tps65010_set_gpio_out_value(unsigned gpio, unsigned value) +{ + int status; + unsigned defgpio; + + if (!the_tps) + return -ENODEV; + if ((gpio < GPIO1) || (gpio > GPIO4)) + return -EINVAL; + + mutex_lock(&the_tps->lock); + + defgpio = i2c_smbus_read_byte_data(the_tps->client, TPS_DEFGPIO); + + /* Configure GPIO for output */ + defgpio |= 1 << (gpio + 3); + + /* Writing 1 forces a logic 0 on that GPIO and vice versa */ + switch (value) { + case LOW: + defgpio |= 1 << (gpio - 1); /* set GPIO low by writing 1 */ + break; + /* case HIGH: */ + default: + defgpio &= ~(1 << (gpio - 1)); /* set GPIO high by writing 0 */ + break; + } + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_DEFGPIO, defgpio); + + pr_debug("%s: gpio%dout = %s, defgpio 0x%02x\n", DRIVER_NAME, + gpio, value ? "high" : "low", + i2c_smbus_read_byte_data(the_tps->client, TPS_DEFGPIO)); + + mutex_unlock(&the_tps->lock); + return status; +} +EXPORT_SYMBOL(tps65010_set_gpio_out_value); + +/*-------------------------------------------------------------------------*/ +/* tps65010_set_led parameter: + * led: LED1 or LED2 + * mode: ON, OFF or BLINK + */ +int tps65010_set_led(unsigned led, unsigned mode) +{ + int status; + unsigned led_on, led_per, offs; + + if (!the_tps) + return -ENODEV; + + if (led == LED1) + offs = 0; + else { + offs = 2; + led = LED2; + } + + mutex_lock(&the_tps->lock); + + pr_debug("%s: led%i_on 0x%02x\n", DRIVER_NAME, led, + i2c_smbus_read_byte_data(the_tps->client, + TPS_LED1_ON + offs)); + + pr_debug("%s: led%i_per 0x%02x\n", DRIVER_NAME, led, + i2c_smbus_read_byte_data(the_tps->client, + TPS_LED1_PER + offs)); + + switch (mode) { + case OFF: + led_on = 1 << 7; + led_per = 0 << 7; + break; + case ON: + led_on = 1 << 7; + led_per = 1 << 7; + break; + case BLINK: + led_on = 0x30 | (0 << 7); + led_per = 0x08 | (1 << 7); + break; + default: + printk(KERN_ERR "%s: Wrong mode parameter for set_led()\n", + DRIVER_NAME); + mutex_unlock(&the_tps->lock); + return -EINVAL; + } + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_LED1_ON + offs, led_on); + + if (status != 0) { + printk(KERN_ERR "%s: Failed to write led%i_on register\n", + DRIVER_NAME, led); + mutex_unlock(&the_tps->lock); + return status; + } + + pr_debug("%s: led%i_on 0x%02x\n", DRIVER_NAME, led, + i2c_smbus_read_byte_data(the_tps->client, TPS_LED1_ON + offs)); + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_LED1_PER + offs, led_per); + + if (status != 0) { + printk(KERN_ERR "%s: Failed to write led%i_per register\n", + DRIVER_NAME, led); + mutex_unlock(&the_tps->lock); + return status; + } + + pr_debug("%s: led%i_per 0x%02x\n", DRIVER_NAME, led, + i2c_smbus_read_byte_data(the_tps->client, + TPS_LED1_PER + offs)); + + mutex_unlock(&the_tps->lock); + + return status; +} +EXPORT_SYMBOL(tps65010_set_led); + +/*-------------------------------------------------------------------------*/ +/* tps65010_set_vib parameter: + * value: ON or OFF + */ +int tps65010_set_vib(unsigned value) +{ + int status; + unsigned vdcdc2; + + if (!the_tps) + return -ENODEV; + + mutex_lock(&the_tps->lock); + + vdcdc2 = i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC2); + vdcdc2 &= ~(1 << 1); + if (value) + vdcdc2 |= (1 << 1); + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_VDCDC2, vdcdc2); + + pr_debug("%s: vibrator %s\n", DRIVER_NAME, value ? "on" : "off"); + + mutex_unlock(&the_tps->lock); + return status; +} +EXPORT_SYMBOL(tps65010_set_vib); + +/*-------------------------------------------------------------------------*/ +/* tps65010_set_low_pwr parameter: + * mode: ON or OFF + */ +int tps65010_set_low_pwr(unsigned mode) +{ + int status; + unsigned vdcdc1; + + if (!the_tps) + return -ENODEV; + + mutex_lock(&the_tps->lock); + + pr_debug("%s: %s low_pwr, vdcdc1 0x%02x\n", DRIVER_NAME, + mode ? "enable" : "disable", + i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1)); + + vdcdc1 = i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1); + + switch (mode) { + case OFF: + vdcdc1 &= ~TPS_ENABLE_LP; /* disable ENABLE_LP bit */ + break; + /* case ON: */ + default: + vdcdc1 |= TPS_ENABLE_LP; /* enable ENABLE_LP bit */ + break; + } + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_VDCDC1, vdcdc1); + + if (status != 0) + printk(KERN_ERR "%s: Failed to write vdcdc1 register\n", + DRIVER_NAME); + else + pr_debug("%s: vdcdc1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1)); + + mutex_unlock(&the_tps->lock); + + return status; +} +EXPORT_SYMBOL(tps65010_set_low_pwr); + +/*-------------------------------------------------------------------------*/ +/* tps65010_config_vregs1 parameter: + * value to be written to VREGS1 register + * Note: The complete register is written, set all bits you need + */ +int tps65010_config_vregs1(unsigned value) +{ + int status; + + if (!the_tps) + return -ENODEV; + + mutex_lock(&the_tps->lock); + + pr_debug("%s: vregs1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(the_tps->client, TPS_VREGS1)); + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_VREGS1, value); + + if (status != 0) + printk(KERN_ERR "%s: Failed to write vregs1 register\n", + DRIVER_NAME); + else + pr_debug("%s: vregs1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(the_tps->client, TPS_VREGS1)); + + mutex_unlock(&the_tps->lock); + + return status; +} +EXPORT_SYMBOL(tps65010_config_vregs1); + +int tps65010_config_vdcdc2(unsigned value) +{ + struct i2c_client *c; + int status; + + if (!the_tps) + return -ENODEV; + + c = the_tps->client; + mutex_lock(&the_tps->lock); + + pr_debug("%s: vdcdc2 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(c, TPS_VDCDC2)); + + status = i2c_smbus_write_byte_data(c, TPS_VDCDC2, value); + + if (status != 0) + printk(KERN_ERR "%s: Failed to write vdcdc2 register\n", + DRIVER_NAME); + else + pr_debug("%s: vregs1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(c, TPS_VDCDC2)); + + mutex_unlock(&the_tps->lock); + return status; +} +EXPORT_SYMBOL(tps65010_config_vdcdc2); + +/*-------------------------------------------------------------------------*/ +/* tps65013_set_low_pwr parameter: + * mode: ON or OFF + */ + +/* FIXME: Assumes AC or USB power is present. Setting AUA bit is not + required if power supply is through a battery */ + +int tps65013_set_low_pwr(unsigned mode) +{ + int status; + unsigned vdcdc1, chgconfig; + + if (!the_tps || the_tps->por) + return -ENODEV; + + mutex_lock(&the_tps->lock); + + pr_debug("%s: %s low_pwr, chgconfig 0x%02x vdcdc1 0x%02x\n", + DRIVER_NAME, + mode ? "enable" : "disable", + i2c_smbus_read_byte_data(the_tps->client, TPS_CHGCONFIG), + i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1)); + + chgconfig = i2c_smbus_read_byte_data(the_tps->client, TPS_CHGCONFIG); + vdcdc1 = i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1); + + switch (mode) { + case OFF: + chgconfig &= ~TPS65013_AUA; /* disable AUA bit */ + vdcdc1 &= ~TPS_ENABLE_LP; /* disable ENABLE_LP bit */ + break; + /* case ON: */ + default: + chgconfig |= TPS65013_AUA; /* enable AUA bit */ + vdcdc1 |= TPS_ENABLE_LP; /* enable ENABLE_LP bit */ + break; + } + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_CHGCONFIG, chgconfig); + if (status != 0) { + printk(KERN_ERR "%s: Failed to write chconfig register\n", + DRIVER_NAME); + mutex_unlock(&the_tps->lock); + return status; + } + + chgconfig = i2c_smbus_read_byte_data(the_tps->client, TPS_CHGCONFIG); + the_tps->chgconf = chgconfig; + show_chgconfig(0, "chgconf", chgconfig); + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_VDCDC1, vdcdc1); + + if (status != 0) + printk(KERN_ERR "%s: Failed to write vdcdc1 register\n", + DRIVER_NAME); + else + pr_debug("%s: vdcdc1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1)); + + mutex_unlock(&the_tps->lock); + + return status; +} +EXPORT_SYMBOL(tps65013_set_low_pwr); + +/*-------------------------------------------------------------------------*/ + +static int __init tps_init(void) +{ + u32 tries = 3; + int status = -ENODEV; + + printk(KERN_INFO "%s: version %s\n", DRIVER_NAME, DRIVER_VERSION); + + /* some boards have startup glitches */ + while (tries--) { + status = i2c_add_driver(&tps65010_driver); + if (the_tps) + break; + i2c_del_driver(&tps65010_driver); + if (!tries) { + printk(KERN_ERR "%s: no chip?\n", DRIVER_NAME); + return -ENODEV; + } + pr_debug("%s: re-probe ...\n", DRIVER_NAME); + msleep(10); + } + + return status; +} +/* NOTE: this MUST be initialized before the other parts of the system + * that rely on it ... but after the i2c bus on which this relies. + * That is, much earlier than on PC-type systems, which don't often use + * I2C as a core system bus. + */ +subsys_initcall(tps_init); + +static void __exit tps_exit(void) +{ + i2c_del_driver(&tps65010_driver); +} +module_exit(tps_exit); + diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c new file mode 100644 index 00000000000..2a760653419 --- /dev/null +++ b/drivers/mfd/twl-core.c @@ -0,0 +1,1076 @@ +/* + * twl_core.c - driver for TWL4030/TWL5030/TWL60X0/TPS659x0 PM + * and audio CODEC devices + * + * Copyright (C) 2005-2006 Texas Instruments, Inc. + * + * Modifications to defer interrupt handling to a kernel thread: + * Copyright (C) 2006 MontaVista Software, Inc. + * + * Based on tlv320aic23.c: + * Copyright (c) by Kai Svahn <kai.svahn@nokia.com> + * + * Code cleanup and modifications to IRQ handler. + * by syed khasim <x0khasim@ti.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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> + +#include <linux/regulator/machine.h> + +#include <linux/i2c.h> +#include <linux/i2c/twl.h> + +#if defined(CONFIG_ARCH_OMAP2) || defined(CONFIG_ARCH_OMAP3) +#include <plat/cpu.h> +#endif + +/* + * The TWL4030 "Triton 2" is one of a family of a multi-function "Power + * Management and System Companion Device" chips originally designed for + * use in OMAP2 and OMAP 3 based systems. Its control interfaces use I2C, + * often at around 3 Mbit/sec, including for interrupt handling. + * + * This driver core provides genirq support for the interrupts emitted, + * by the various modules, and exports register access primitives. + * + * FIXME this driver currently requires use of the first interrupt line + * (and associated registers). + */ + +#define DRIVER_NAME "twl" + +#if defined(CONFIG_TWL4030_BCI_BATTERY) || \ + defined(CONFIG_TWL4030_BCI_BATTERY_MODULE) +#define twl_has_bci() true +#else +#define twl_has_bci() false +#endif + +#if defined(CONFIG_KEYBOARD_TWL4030) || defined(CONFIG_KEYBOARD_TWL4030_MODULE) +#define twl_has_keypad() true +#else +#define twl_has_keypad() false +#endif + +#if defined(CONFIG_GPIO_TWL4030) || defined(CONFIG_GPIO_TWL4030_MODULE) +#define twl_has_gpio() true +#else +#define twl_has_gpio() false +#endif + +#if defined(CONFIG_REGULATOR_TWL4030) \ + || defined(CONFIG_REGULATOR_TWL4030_MODULE) +#define twl_has_regulator() true +#else +#define twl_has_regulator() false +#endif + +#if defined(CONFIG_TWL4030_MADC) || defined(CONFIG_TWL4030_MADC_MODULE) +#define twl_has_madc() true +#else +#define twl_has_madc() false +#endif + +#ifdef CONFIG_TWL4030_POWER +#define twl_has_power() true +#else +#define twl_has_power() false +#endif + +#if defined(CONFIG_RTC_DRV_TWL4030) || defined(CONFIG_RTC_DRV_TWL4030_MODULE) +#define twl_has_rtc() true +#else +#define twl_has_rtc() false +#endif + +#if defined(CONFIG_TWL4030_USB) || defined(CONFIG_TWL4030_USB_MODULE) +#define twl_has_usb() true +#else +#define twl_has_usb() false +#endif + +#if defined(CONFIG_TWL4030_WATCHDOG) || \ + defined(CONFIG_TWL4030_WATCHDOG_MODULE) +#define twl_has_watchdog() true +#else +#define twl_has_watchdog() false +#endif + +#if defined(CONFIG_TWL4030_CODEC) || defined(CONFIG_TWL4030_CODEC_MODULE) +#define twl_has_codec() true +#else +#define twl_has_codec() false +#endif + +/* Triton Core internal information (BEGIN) */ + +/* Last - for index max*/ +#define TWL4030_MODULE_LAST TWL4030_MODULE_SECURED_REG + +#define TWL_NUM_SLAVES 4 + +#if defined(CONFIG_INPUT_TWL4030_PWRBUTTON) \ + || defined(CONFIG_INPUT_TWL4030_PWBUTTON_MODULE) +#define twl_has_pwrbutton() true +#else +#define twl_has_pwrbutton() false +#endif + +#define SUB_CHIP_ID0 0 +#define SUB_CHIP_ID1 1 +#define SUB_CHIP_ID2 2 +#define SUB_CHIP_ID3 3 + +#define TWL_MODULE_LAST TWL4030_MODULE_LAST + +/* Base Address defns for twl4030_map[] */ + +/* subchip/slave 0 - USB ID */ +#define TWL4030_BASEADD_USB 0x0000 + +/* subchip/slave 1 - AUD ID */ +#define TWL4030_BASEADD_AUDIO_VOICE 0x0000 +#define TWL4030_BASEADD_GPIO 0x0098 +#define TWL4030_BASEADD_INTBR 0x0085 +#define TWL4030_BASEADD_PIH 0x0080 +#define TWL4030_BASEADD_TEST 0x004C + +/* subchip/slave 2 - AUX ID */ +#define TWL4030_BASEADD_INTERRUPTS 0x00B9 +#define TWL4030_BASEADD_LED 0x00EE +#define TWL4030_BASEADD_MADC 0x0000 +#define TWL4030_BASEADD_MAIN_CHARGE 0x0074 +#define TWL4030_BASEADD_PRECHARGE 0x00AA +#define TWL4030_BASEADD_PWM0 0x00F8 +#define TWL4030_BASEADD_PWM1 0x00FB +#define TWL4030_BASEADD_PWMA 0x00EF +#define TWL4030_BASEADD_PWMB 0x00F1 +#define TWL4030_BASEADD_KEYPAD 0x00D2 + +#define TWL5031_BASEADD_ACCESSORY 0x0074 /* Replaces Main Charge */ +#define TWL5031_BASEADD_INTERRUPTS 0x00B9 /* Different than TWL4030's + one */ + +/* subchip/slave 3 - POWER ID */ +#define TWL4030_BASEADD_BACKUP 0x0014 +#define TWL4030_BASEADD_INT 0x002E +#define TWL4030_BASEADD_PM_MASTER 0x0036 +#define TWL4030_BASEADD_PM_RECEIVER 0x005B +#define TWL4030_BASEADD_RTC 0x001C +#define TWL4030_BASEADD_SECURED_REG 0x0000 + +/* Triton Core internal information (END) */ + + +/* subchip/slave 0 0x48 - POWER */ +#define TWL6030_BASEADD_RTC 0x0000 +#define TWL6030_BASEADD_MEM 0x0017 +#define TWL6030_BASEADD_PM_MASTER 0x001F +#define TWL6030_BASEADD_PM_SLAVE_MISC 0x0030 /* PM_RECEIVER */ +#define TWL6030_BASEADD_PM_MISC 0x00E2 +#define TWL6030_BASEADD_PM_PUPD 0x00F0 + +/* subchip/slave 1 0x49 - FEATURE */ +#define TWL6030_BASEADD_USB 0x0000 +#define TWL6030_BASEADD_GPADC_CTRL 0x002E +#define TWL6030_BASEADD_AUX 0x0090 +#define TWL6030_BASEADD_PWM 0x00BA +#define TWL6030_BASEADD_GASGAUGE 0x00C0 +#define TWL6030_BASEADD_PIH 0x00D0 +#define TWL6030_BASEADD_CHARGER 0x00E0 + +/* subchip/slave 2 0x4A - DFT */ +#define TWL6030_BASEADD_DIEID 0x00C0 + +/* subchip/slave 3 0x4B - AUDIO */ +#define TWL6030_BASEADD_AUDIO 0x0000 +#define TWL6030_BASEADD_RSV 0x0000 + +/* Few power values */ +#define R_CFG_BOOT 0x05 +#define R_PROTECT_KEY 0x0E + +/* access control values for R_PROTECT_KEY */ +#define KEY_UNLOCK1 0xce +#define KEY_UNLOCK2 0xec +#define KEY_LOCK 0x00 + +/* some fields in R_CFG_BOOT */ +#define HFCLK_FREQ_19p2_MHZ (1 << 0) +#define HFCLK_FREQ_26_MHZ (2 << 0) +#define HFCLK_FREQ_38p4_MHZ (3 << 0) +#define HIGH_PERF_SQ (1 << 3) +#define CK32K_LOWPWR_EN (1 << 7) + + +/* chip-specific feature flags, for i2c_device_id.driver_data */ +#define TWL4030_VAUX2 BIT(0) /* pre-5030 voltage ranges */ +#define TPS_SUBSET BIT(1) /* tps659[23]0 have fewer LDOs */ +#define TWL5031 BIT(2) /* twl5031 has different registers */ +#define TWL6030_CLASS BIT(3) /* TWL6030 class */ + +/*----------------------------------------------------------------------*/ + +/* is driver active, bound to a chip? */ +static bool inuse; + +static unsigned int twl_id; +unsigned int twl_rev(void) +{ + return twl_id; +} +EXPORT_SYMBOL(twl_rev); + +/* Structure for each TWL4030/TWL6030 Slave */ +struct twl_client { + struct i2c_client *client; + u8 address; + + /* max numb of i2c_msg required is for read =2 */ + struct i2c_msg xfer_msg[2]; + + /* To lock access to xfer_msg */ + struct mutex xfer_lock; +}; + +static struct twl_client twl_modules[TWL_NUM_SLAVES]; + + +/* mapping the module id to slave id and base address */ +struct twl_mapping { + unsigned char sid; /* Slave ID */ + unsigned char base; /* base address */ +}; +struct twl_mapping *twl_map; + +static struct twl_mapping twl4030_map[TWL4030_MODULE_LAST + 1] = { + /* + * NOTE: don't change this table without updating the + * <linux/i2c/twl.h> defines for TWL4030_MODULE_* + * so they continue to match the order in this table. + */ + + { 0, TWL4030_BASEADD_USB }, + + { 1, TWL4030_BASEADD_AUDIO_VOICE }, + { 1, TWL4030_BASEADD_GPIO }, + { 1, TWL4030_BASEADD_INTBR }, + { 1, TWL4030_BASEADD_PIH }, + { 1, TWL4030_BASEADD_TEST }, + + { 2, TWL4030_BASEADD_KEYPAD }, + { 2, TWL4030_BASEADD_MADC }, + { 2, TWL4030_BASEADD_INTERRUPTS }, + { 2, TWL4030_BASEADD_LED }, + { 2, TWL4030_BASEADD_MAIN_CHARGE }, + { 2, TWL4030_BASEADD_PRECHARGE }, + { 2, TWL4030_BASEADD_PWM0 }, + { 2, TWL4030_BASEADD_PWM1 }, + { 2, TWL4030_BASEADD_PWMA }, + { 2, TWL4030_BASEADD_PWMB }, + { 2, TWL5031_BASEADD_ACCESSORY }, + { 2, TWL5031_BASEADD_INTERRUPTS }, + + { 3, TWL4030_BASEADD_BACKUP }, + { 3, TWL4030_BASEADD_INT }, + { 3, TWL4030_BASEADD_PM_MASTER }, + { 3, TWL4030_BASEADD_PM_RECEIVER }, + { 3, TWL4030_BASEADD_RTC }, + { 3, TWL4030_BASEADD_SECURED_REG }, +}; + +static struct twl_mapping twl6030_map[] = { + /* + * NOTE: don't change this table without updating the + * <linux/i2c/twl.h> defines for TWL4030_MODULE_* + * so they continue to match the order in this table. + */ + { SUB_CHIP_ID1, TWL6030_BASEADD_USB }, + { SUB_CHIP_ID3, TWL6030_BASEADD_AUDIO }, + { SUB_CHIP_ID2, TWL6030_BASEADD_DIEID }, + { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, + { SUB_CHIP_ID1, TWL6030_BASEADD_PIH }, + + { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, + { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, + { SUB_CHIP_ID1, TWL6030_BASEADD_GPADC_CTRL }, + { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, + { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, + + { SUB_CHIP_ID1, TWL6030_BASEADD_CHARGER }, + { SUB_CHIP_ID1, TWL6030_BASEADD_GASGAUGE }, + { SUB_CHIP_ID1, TWL6030_BASEADD_PWM }, + { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, + { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, + + { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, + { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, + { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, + { SUB_CHIP_ID0, TWL6030_BASEADD_PM_MASTER }, + { SUB_CHIP_ID0, TWL6030_BASEADD_PM_SLAVE_MISC }, + + { SUB_CHIP_ID0, TWL6030_BASEADD_RTC }, + { SUB_CHIP_ID0, TWL6030_BASEADD_MEM }, +}; + +/*----------------------------------------------------------------------*/ + +/* Exported Functions */ + +/** + * twl_i2c_write - Writes a n bit register in TWL4030/TWL5030/TWL60X0 + * @mod_no: module number + * @value: an array of num_bytes+1 containing data to write + * @reg: register address (just offset will do) + * @num_bytes: number of bytes to transfer + * + * IMPORTANT: for 'value' parameter: Allocate value num_bytes+1 and + * valid data starts at Offset 1. + * + * Returns the result of operation - 0 is success + */ +int twl_i2c_write(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes) +{ + int ret; + int sid; + struct twl_client *twl; + struct i2c_msg *msg; + + if (unlikely(mod_no > TWL_MODULE_LAST)) { + pr_err("%s: invalid module number %d\n", DRIVER_NAME, mod_no); + return -EPERM; + } + sid = twl_map[mod_no].sid; + twl = &twl_modules[sid]; + + if (unlikely(!inuse)) { + pr_err("%s: client %d is not initialized\n", DRIVER_NAME, sid); + return -EPERM; + } + mutex_lock(&twl->xfer_lock); + /* + * [MSG1]: fill the register address data + * fill the data Tx buffer + */ + msg = &twl->xfer_msg[0]; + msg->addr = twl->address; + msg->len = num_bytes + 1; + msg->flags = 0; + msg->buf = value; + /* over write the first byte of buffer with the register address */ + *value = twl_map[mod_no].base + reg; + ret = i2c_transfer(twl->client->adapter, twl->xfer_msg, 1); + mutex_unlock(&twl->xfer_lock); + + /* i2c_transfer returns number of messages transferred */ + if (ret != 1) { + pr_err("%s: i2c_write failed to transfer all messages\n", + DRIVER_NAME); + if (ret < 0) + return ret; + else + return -EIO; + } else { + return 0; + } +} +EXPORT_SYMBOL(twl_i2c_write); + +/** + * twl_i2c_read - Reads a n bit register in TWL4030/TWL5030/TWL60X0 + * @mod_no: module number + * @value: an array of num_bytes containing data to be read + * @reg: register address (just offset will do) + * @num_bytes: number of bytes to transfer + * + * Returns result of operation - num_bytes is success else failure. + */ +int twl_i2c_read(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes) +{ + int ret; + u8 val; + int sid; + struct twl_client *twl; + struct i2c_msg *msg; + + if (unlikely(mod_no > TWL_MODULE_LAST)) { + pr_err("%s: invalid module number %d\n", DRIVER_NAME, mod_no); + return -EPERM; + } + sid = twl_map[mod_no].sid; + twl = &twl_modules[sid]; + + if (unlikely(!inuse)) { + pr_err("%s: client %d is not initialized\n", DRIVER_NAME, sid); + return -EPERM; + } + mutex_lock(&twl->xfer_lock); + /* [MSG1] fill the register address data */ + msg = &twl->xfer_msg[0]; + msg->addr = twl->address; + msg->len = 1; + msg->flags = 0; /* Read the register value */ + val = twl_map[mod_no].base + reg; + msg->buf = &val; + /* [MSG2] fill the data rx buffer */ + msg = &twl->xfer_msg[1]; + msg->addr = twl->address; + msg->flags = I2C_M_RD; /* Read the register value */ + msg->len = num_bytes; /* only n bytes */ + msg->buf = value; + ret = i2c_transfer(twl->client->adapter, twl->xfer_msg, 2); + mutex_unlock(&twl->xfer_lock); + + /* i2c_transfer returns number of messages transferred */ + if (ret != 2) { + pr_err("%s: i2c_read failed to transfer all messages\n", + DRIVER_NAME); + if (ret < 0) + return ret; + else + return -EIO; + } else { + return 0; + } +} +EXPORT_SYMBOL(twl_i2c_read); + +/** + * twl_i2c_write_u8 - Writes a 8 bit register in TWL4030/TWL5030/TWL60X0 + * @mod_no: module number + * @value: the value to be written 8 bit + * @reg: register address (just offset will do) + * + * Returns result of operation - 0 is success + */ +int twl_i2c_write_u8(u8 mod_no, u8 value, u8 reg) +{ + + /* 2 bytes offset 1 contains the data offset 0 is used by i2c_write */ + u8 temp_buffer[2] = { 0 }; + /* offset 1 contains the data */ + temp_buffer[1] = value; + return twl_i2c_write(mod_no, temp_buffer, reg, 1); +} +EXPORT_SYMBOL(twl_i2c_write_u8); + +/** + * twl_i2c_read_u8 - Reads a 8 bit register from TWL4030/TWL5030/TWL60X0 + * @mod_no: module number + * @value: the value read 8 bit + * @reg: register address (just offset will do) + * + * Returns result of operation - 0 is success + */ +int twl_i2c_read_u8(u8 mod_no, u8 *value, u8 reg) +{ + return twl_i2c_read(mod_no, value, reg, 1); +} +EXPORT_SYMBOL(twl_i2c_read_u8); + +/*----------------------------------------------------------------------*/ + +static struct device * +add_numbered_child(unsigned chip, const char *name, int num, + void *pdata, unsigned pdata_len, + bool can_wakeup, int irq0, int irq1) +{ + struct platform_device *pdev; + struct twl_client *twl = &twl_modules[chip]; + int status; + + pdev = platform_device_alloc(name, num); + if (!pdev) { + dev_dbg(&twl->client->dev, "can't alloc dev\n"); + status = -ENOMEM; + goto err; + } + + device_init_wakeup(&pdev->dev, can_wakeup); + pdev->dev.parent = &twl->client->dev; + + if (pdata) { + status = platform_device_add_data(pdev, pdata, pdata_len); + if (status < 0) { + dev_dbg(&pdev->dev, "can't add platform_data\n"); + goto err; + } + } + + if (irq0) { + struct resource r[2] = { + { .start = irq0, .flags = IORESOURCE_IRQ, }, + { .start = irq1, .flags = IORESOURCE_IRQ, }, + }; + + status = platform_device_add_resources(pdev, r, irq1 ? 2 : 1); + if (status < 0) { + dev_dbg(&pdev->dev, "can't add irqs\n"); + goto err; + } + } + + status = platform_device_add(pdev); + +err: + if (status < 0) { + platform_device_put(pdev); + dev_err(&twl->client->dev, "can't add %s dev\n", name); + return ERR_PTR(status); + } + return &pdev->dev; +} + +static inline struct device *add_child(unsigned chip, const char *name, + void *pdata, unsigned pdata_len, + bool can_wakeup, int irq0, int irq1) +{ + return add_numbered_child(chip, name, -1, pdata, pdata_len, + can_wakeup, irq0, irq1); +} + +static struct device * +add_regulator_linked(int num, struct regulator_init_data *pdata, + struct regulator_consumer_supply *consumers, + unsigned num_consumers) +{ + unsigned sub_chip_id; + /* regulator framework demands init_data ... */ + if (!pdata) + return NULL; + + if (consumers) { + pdata->consumer_supplies = consumers; + pdata->num_consumer_supplies = num_consumers; + } + + /* NOTE: we currently ignore regulator IRQs, e.g. for short circuits */ + sub_chip_id = twl_map[TWL_MODULE_PM_MASTER].sid; + return add_numbered_child(sub_chip_id, "twl_reg", num, + pdata, sizeof(*pdata), false, 0, 0); +} + +static struct device * +add_regulator(int num, struct regulator_init_data *pdata) +{ + return add_regulator_linked(num, pdata, NULL, 0); +} + +/* + * NOTE: We know the first 8 IRQs after pdata->base_irq are + * for the PIH, and the next are for the PWR_INT SIH, since + * that's how twl_init_irq() sets things up. + */ + +static int +add_children(struct twl4030_platform_data *pdata, unsigned long features) +{ + struct device *child; + unsigned sub_chip_id; + + if (twl_has_bci() && pdata->bci && + !(features & (TPS_SUBSET | TWL5031))) { + child = add_child(3, "twl4030_bci", + pdata->bci, sizeof(*pdata->bci), + false, + /* irq0 = CHG_PRES, irq1 = BCI */ + pdata->irq_base + BCI_PRES_INTR_OFFSET, + pdata->irq_base + BCI_INTR_OFFSET); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (twl_has_gpio() && pdata->gpio) { + child = add_child(SUB_CHIP_ID1, "twl4030_gpio", + pdata->gpio, sizeof(*pdata->gpio), + false, pdata->irq_base + GPIO_INTR_OFFSET, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (twl_has_keypad() && pdata->keypad) { + child = add_child(SUB_CHIP_ID2, "twl4030_keypad", + pdata->keypad, sizeof(*pdata->keypad), + true, pdata->irq_base + KEYPAD_INTR_OFFSET, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (twl_has_madc() && pdata->madc) { + child = add_child(2, "twl4030_madc", + pdata->madc, sizeof(*pdata->madc), + true, pdata->irq_base + MADC_INTR_OFFSET, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (twl_has_rtc()) { + /* + * REVISIT platform_data here currently might expose the + * "msecure" line ... but for now we just expect board + * setup to tell the chip "it's always ok to SET_TIME". + * Eventually, Linux might become more aware of such + * HW security concerns, and "least privilege". + */ + sub_chip_id = twl_map[TWL_MODULE_RTC].sid; + child = add_child(sub_chip_id, "twl_rtc", + NULL, 0, + true, pdata->irq_base + RTC_INTR_OFFSET, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (twl_has_usb() && pdata->usb && twl_class_is_4030()) { + + static struct regulator_consumer_supply usb1v5 = { + .supply = "usb1v5", + }; + static struct regulator_consumer_supply usb1v8 = { + .supply = "usb1v8", + }; + static struct regulator_consumer_supply usb3v1 = { + .supply = "usb3v1", + }; + + /* First add the regulators so that they can be used by transceiver */ + if (twl_has_regulator()) { + /* this is a template that gets copied */ + struct regulator_init_data usb_fixed = { + .constraints.valid_modes_mask = + REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .constraints.valid_ops_mask = + REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }; + + child = add_regulator_linked(TWL4030_REG_VUSB1V5, + &usb_fixed, &usb1v5, 1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator_linked(TWL4030_REG_VUSB1V8, + &usb_fixed, &usb1v8, 1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator_linked(TWL4030_REG_VUSB3V1, + &usb_fixed, &usb3v1, 1); + if (IS_ERR(child)) + return PTR_ERR(child); + + } + + child = add_child(0, "twl4030_usb", + pdata->usb, sizeof(*pdata->usb), + true, + /* irq0 = USB_PRES, irq1 = USB */ + pdata->irq_base + USB_PRES_INTR_OFFSET, + pdata->irq_base + USB_INTR_OFFSET); + + if (IS_ERR(child)) + return PTR_ERR(child); + + /* we need to connect regulators to this transceiver */ + if (twl_has_regulator() && child) { + usb1v5.dev = child; + usb1v8.dev = child; + usb3v1.dev = child; + } + } + + if (twl_has_watchdog()) { + child = add_child(0, "twl4030_wdt", NULL, 0, false, 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (twl_has_pwrbutton()) { + child = add_child(1, "twl4030_pwrbutton", + NULL, 0, true, pdata->irq_base + 8 + 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (twl_has_codec() && pdata->codec) { + child = add_child(1, "twl4030_codec", + pdata->codec, sizeof(*pdata->codec), + false, 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + /* twl4030 regulators */ + if (twl_has_regulator() && twl_class_is_4030()) { + child = add_regulator(TWL4030_REG_VPLL1, pdata->vpll1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VIO, pdata->vio); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VDD1, pdata->vdd1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VDD2, pdata->vdd2); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VMMC1, pdata->vmmc1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VDAC, pdata->vdac); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator((features & TWL4030_VAUX2) + ? TWL4030_REG_VAUX2_4030 + : TWL4030_REG_VAUX2, + pdata->vaux2); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VINTANA1, pdata->vintana1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VINTANA2, pdata->vintana2); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VINTDIG, pdata->vintdig); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + /* maybe add LDOs that are omitted on cost-reduced parts */ + if (twl_has_regulator() && !(features & TPS_SUBSET) + && twl_class_is_4030()) { + child = add_regulator(TWL4030_REG_VPLL2, pdata->vpll2); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VMMC2, pdata->vmmc2); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VSIM, pdata->vsim); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VAUX1, pdata->vaux1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VAUX3, pdata->vaux3); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VAUX4, pdata->vaux4); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + /* twl6030 regulators */ + if (twl_has_regulator() && twl_class_is_6030()) { + child = add_regulator(TWL6030_REG_VMMC, pdata->vmmc); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VPP, pdata->vpp); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VUSIM, pdata->vusim); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VANA, pdata->vana); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VCXIO, pdata->vcxio); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VDAC, pdata->vdac); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VUSB, pdata->vusb); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VAUX1_6030, pdata->vaux1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VAUX2_6030, pdata->vaux2); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VAUX3_6030, pdata->vaux3); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + return 0; +} + +/*----------------------------------------------------------------------*/ + +/* + * These three functions initialize the on-chip clock framework, + * letting it generate the right frequencies for USB, MADC, and + * other purposes. + */ +static inline int __init protect_pm_master(void) +{ + int e = 0; + + e = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, KEY_LOCK, + R_PROTECT_KEY); + return e; +} + +static inline int __init unprotect_pm_master(void) +{ + int e = 0; + + e |= twl_i2c_write_u8(TWL_MODULE_PM_MASTER, KEY_UNLOCK1, + R_PROTECT_KEY); + e |= twl_i2c_write_u8(TWL_MODULE_PM_MASTER, KEY_UNLOCK2, + R_PROTECT_KEY); + return e; +} + +static void clocks_init(struct device *dev, + struct twl4030_clock_init_data *clock) +{ + int e = 0; + struct clk *osc; + u32 rate; + u8 ctrl = HFCLK_FREQ_26_MHZ; + +#if defined(CONFIG_ARCH_OMAP2) || defined(CONFIG_ARCH_OMAP3) + if (cpu_is_omap2430()) + osc = clk_get(dev, "osc_ck"); + else + osc = clk_get(dev, "osc_sys_ck"); + + if (IS_ERR(osc)) { + printk(KERN_WARNING "Skipping twl internal clock init and " + "using bootloader value (unknown osc rate)\n"); + return; + } + + rate = clk_get_rate(osc); + clk_put(osc); + +#else + /* REVISIT for non-OMAP systems, pass the clock rate from + * board init code, using platform_data. + */ + osc = ERR_PTR(-EIO); + + printk(KERN_WARNING "Skipping twl internal clock init and " + "using bootloader value (unknown osc rate)\n"); + + return; +#endif + + switch (rate) { + case 19200000: + ctrl = HFCLK_FREQ_19p2_MHZ; + break; + case 26000000: + ctrl = HFCLK_FREQ_26_MHZ; + break; + case 38400000: + ctrl = HFCLK_FREQ_38p4_MHZ; + break; + } + + ctrl |= HIGH_PERF_SQ; + if (clock && clock->ck32k_lowpwr_enable) + ctrl |= CK32K_LOWPWR_EN; + + e |= unprotect_pm_master(); + /* effect->MADC+USB ck en */ + e |= twl_i2c_write_u8(TWL_MODULE_PM_MASTER, ctrl, R_CFG_BOOT); + e |= protect_pm_master(); + + if (e < 0) + pr_err("%s: clock init err [%d]\n", DRIVER_NAME, e); +} + +/*----------------------------------------------------------------------*/ + +int twl4030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end); +int twl4030_exit_irq(void); +int twl4030_init_chip_irq(const char *chip); +int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end); +int twl6030_exit_irq(void); + +static int twl_remove(struct i2c_client *client) +{ + unsigned i; + int status; + + if (twl_class_is_4030()) + status = twl4030_exit_irq(); + else + status = twl6030_exit_irq(); + + if (status < 0) + return status; + + for (i = 0; i < TWL_NUM_SLAVES; i++) { + struct twl_client *twl = &twl_modules[i]; + + if (twl->client && twl->client != client) + i2c_unregister_device(twl->client); + twl_modules[i].client = NULL; + } + inuse = false; + return 0; +} + +/* NOTE: this driver only handles a single twl4030/tps659x0 chip */ +static int __init +twl_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int status; + unsigned i; + struct twl4030_platform_data *pdata = client->dev.platform_data; + + if (!pdata) { + dev_dbg(&client->dev, "no platform data?\n"); + return -EINVAL; + } + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C) == 0) { + dev_dbg(&client->dev, "can't talk I2C?\n"); + return -EIO; + } + + if (inuse) { + dev_dbg(&client->dev, "driver is already in use\n"); + return -EBUSY; + } + + for (i = 0; i < TWL_NUM_SLAVES; i++) { + struct twl_client *twl = &twl_modules[i]; + + twl->address = client->addr + i; + if (i == 0) + twl->client = client; + else { + twl->client = i2c_new_dummy(client->adapter, + twl->address); + if (!twl->client) { + dev_err(&client->dev, + "can't attach client %d\n", i); + status = -ENOMEM; + goto fail; + } + } + mutex_init(&twl->xfer_lock); + } + inuse = true; + if ((id->driver_data) & TWL6030_CLASS) { + twl_id = TWL6030_CLASS_ID; + twl_map = &twl6030_map[0]; + } else { + twl_id = TWL4030_CLASS_ID; + twl_map = &twl4030_map[0]; + } + + /* setup clock framework */ + clocks_init(&client->dev, pdata->clock); + + /* load power event scripts */ + if (twl_has_power() && pdata->power) + twl4030_power_init(pdata->power); + + /* Maybe init the T2 Interrupt subsystem */ + if (client->irq + && pdata->irq_base + && pdata->irq_end > pdata->irq_base) { + if (twl_class_is_4030()) { + twl4030_init_chip_irq(id->name); + status = twl4030_init_irq(client->irq, pdata->irq_base, + pdata->irq_end); + } else { + status = twl6030_init_irq(client->irq, pdata->irq_base, + pdata->irq_end); + } + + if (status < 0) + goto fail; + } + + status = add_children(pdata, id->driver_data); +fail: + if (status < 0) + twl_remove(client); + return status; +} + +static const struct i2c_device_id twl_ids[] = { + { "twl4030", TWL4030_VAUX2 }, /* "Triton 2" */ + { "twl5030", 0 }, /* T2 updated */ + { "twl5031", TWL5031 }, /* TWL5030 updated */ + { "tps65950", 0 }, /* catalog version of twl5030 */ + { "tps65930", TPS_SUBSET }, /* fewer LDOs and DACs; no charger */ + { "tps65920", TPS_SUBSET }, /* fewer LDOs; no codec or charger */ + { "twl6030", TWL6030_CLASS }, /* "Phoenix power chip" */ + { /* end of list */ }, +}; +MODULE_DEVICE_TABLE(i2c, twl_ids); + +/* One Client Driver , 4 Clients */ +static struct i2c_driver twl_driver = { + .driver.name = DRIVER_NAME, + .id_table = twl_ids, + .probe = twl_probe, + .remove = twl_remove, +}; + +static int __init twl_init(void) +{ + return i2c_add_driver(&twl_driver); +} +subsys_initcall(twl_init); + +static void __exit twl_exit(void) +{ + i2c_del_driver(&twl_driver); +} +module_exit(twl_exit); + +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("I2C Core interface for TWL"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/twl4030-codec.c b/drivers/mfd/twl4030-codec.c new file mode 100644 index 00000000000..700b149c1b9 --- /dev/null +++ b/drivers/mfd/twl4030-codec.c @@ -0,0 +1,276 @@ +/* + * MFD driver for twl4030 codec submodule + * + * Author: Peter Ujfalusi <peter.ujfalusi@nokia.com> + * + * Copyright: (C) 2009 Nokia Corporation + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/platform_device.h> +#include <linux/i2c/twl.h> +#include <linux/mfd/core.h> +#include <linux/mfd/twl4030-codec.h> + +#define TWL4030_CODEC_CELLS 2 + +static struct platform_device *twl4030_codec_dev; + +struct twl4030_codec_resource { + int request_count; + u8 reg; + u8 mask; +}; + +struct twl4030_codec { + unsigned int audio_mclk; + struct mutex mutex; + struct twl4030_codec_resource resource[TWL4030_CODEC_RES_MAX]; + struct mfd_cell cells[TWL4030_CODEC_CELLS]; +}; + +/* + * Modify the resource, the function returns the content of the register + * after the modification. + */ +static int twl4030_codec_set_resource(enum twl4030_codec_res id, int enable) +{ + struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); + u8 val; + + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, + codec->resource[id].reg); + + if (enable) + val |= codec->resource[id].mask; + else + val &= ~codec->resource[id].mask; + + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + val, codec->resource[id].reg); + + return val; +} + +static inline int twl4030_codec_get_resource(enum twl4030_codec_res id) +{ + struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); + u8 val; + + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, + codec->resource[id].reg); + + return val; +} + +/* + * Enable the resource. + * The function returns with error or the content of the register + */ +int twl4030_codec_enable_resource(enum twl4030_codec_res id) +{ + struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); + int val; + + if (id >= TWL4030_CODEC_RES_MAX) { + dev_err(&twl4030_codec_dev->dev, + "Invalid resource ID (%u)\n", id); + return -EINVAL; + } + + mutex_lock(&codec->mutex); + if (!codec->resource[id].request_count) + /* Resource was disabled, enable it */ + val = twl4030_codec_set_resource(id, 1); + else + val = twl4030_codec_get_resource(id); + + codec->resource[id].request_count++; + mutex_unlock(&codec->mutex); + + return val; +} +EXPORT_SYMBOL_GPL(twl4030_codec_enable_resource); + +/* + * Disable the resource. + * The function returns with error or the content of the register + */ +int twl4030_codec_disable_resource(unsigned id) +{ + struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); + int val; + + if (id >= TWL4030_CODEC_RES_MAX) { + dev_err(&twl4030_codec_dev->dev, + "Invalid resource ID (%u)\n", id); + return -EINVAL; + } + + mutex_lock(&codec->mutex); + if (!codec->resource[id].request_count) { + dev_err(&twl4030_codec_dev->dev, + "Resource has been disabled already (%u)\n", id); + mutex_unlock(&codec->mutex); + return -EPERM; + } + codec->resource[id].request_count--; + + if (!codec->resource[id].request_count) + /* Resource can be disabled now */ + val = twl4030_codec_set_resource(id, 0); + else + val = twl4030_codec_get_resource(id); + + mutex_unlock(&codec->mutex); + + return val; +} +EXPORT_SYMBOL_GPL(twl4030_codec_disable_resource); + +unsigned int twl4030_codec_get_mclk(void) +{ + struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); + + return codec->audio_mclk; +} +EXPORT_SYMBOL_GPL(twl4030_codec_get_mclk); + +static int __devinit twl4030_codec_probe(struct platform_device *pdev) +{ + struct twl4030_codec *codec; + struct twl4030_codec_data *pdata = pdev->dev.platform_data; + struct mfd_cell *cell = NULL; + int ret, childs = 0; + u8 val; + + if (!pdata) { + dev_err(&pdev->dev, "Platform data is missing\n"); + return -EINVAL; + } + + /* Configure APLL_INFREQ and disable APLL if enabled */ + val = 0; + switch (pdata->audio_mclk) { + case 19200000: + val |= TWL4030_APLL_INFREQ_19200KHZ; + break; + case 26000000: + val |= TWL4030_APLL_INFREQ_26000KHZ; + break; + case 38400000: + val |= TWL4030_APLL_INFREQ_38400KHZ; + break; + default: + dev_err(&pdev->dev, "Invalid audio_mclk\n"); + return -EINVAL; + } + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + val, TWL4030_REG_APLL_CTL); + + codec = kzalloc(sizeof(struct twl4030_codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + + platform_set_drvdata(pdev, codec); + + twl4030_codec_dev = pdev; + mutex_init(&codec->mutex); + codec->audio_mclk = pdata->audio_mclk; + + /* Codec power */ + codec->resource[TWL4030_CODEC_RES_POWER].reg = TWL4030_REG_CODEC_MODE; + codec->resource[TWL4030_CODEC_RES_POWER].mask = TWL4030_CODECPDZ; + + /* PLL */ + codec->resource[TWL4030_CODEC_RES_APLL].reg = TWL4030_REG_APLL_CTL; + codec->resource[TWL4030_CODEC_RES_APLL].mask = TWL4030_APLL_EN; + + if (pdata->audio) { + cell = &codec->cells[childs]; + cell->name = "twl4030_codec_audio"; + cell->platform_data = pdata->audio; + cell->data_size = sizeof(*pdata->audio); + childs++; + } + if (pdata->vibra) { + cell = &codec->cells[childs]; + cell->name = "twl4030_codec_vibra"; + cell->platform_data = pdata->vibra; + cell->data_size = sizeof(*pdata->vibra); + childs++; + } + + if (childs) + ret = mfd_add_devices(&pdev->dev, pdev->id, codec->cells, + childs, NULL, 0); + else { + dev_err(&pdev->dev, "No platform data found for childs\n"); + ret = -ENODEV; + } + + if (!ret) + return 0; + + platform_set_drvdata(pdev, NULL); + kfree(codec); + twl4030_codec_dev = NULL; + return ret; +} + +static int __devexit twl4030_codec_remove(struct platform_device *pdev) +{ + struct twl4030_codec *codec = platform_get_drvdata(pdev); + + mfd_remove_devices(&pdev->dev); + platform_set_drvdata(pdev, NULL); + kfree(codec); + twl4030_codec_dev = NULL; + + return 0; +} + +MODULE_ALIAS("platform:twl4030_codec"); + +static struct platform_driver twl4030_codec_driver = { + .probe = twl4030_codec_probe, + .remove = __devexit_p(twl4030_codec_remove), + .driver = { + .owner = THIS_MODULE, + .name = "twl4030_codec", + }, +}; + +static int __devinit twl4030_codec_init(void) +{ + return platform_driver_register(&twl4030_codec_driver); +} +module_init(twl4030_codec_init); + +static void __devexit twl4030_codec_exit(void) +{ + platform_driver_unregister(&twl4030_codec_driver); +} +module_exit(twl4030_codec_exit); + +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@nokia.com>"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/mfd/twl4030-irq.c b/drivers/mfd/twl4030-irq.c new file mode 100644 index 00000000000..9df9a5ad38f --- /dev/null +++ b/drivers/mfd/twl4030-irq.c @@ -0,0 +1,882 @@ +/* + * twl4030-irq.c - TWL4030/TPS659x0 irq support + * + * Copyright (C) 2005-2006 Texas Instruments, Inc. + * + * Modifications to defer interrupt handling to a kernel thread: + * Copyright (C) 2006 MontaVista Software, Inc. + * + * Based on tlv320aic23.c: + * Copyright (c) by Kai Svahn <kai.svahn@nokia.com> + * + * Code cleanup and modifications to IRQ handler. + * by syed khasim <x0khasim@ti.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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kthread.h> + +#include <linux/i2c/twl.h> + + +/* + * TWL4030 IRQ handling has two stages in hardware, and thus in software. + * The Primary Interrupt Handler (PIH) stage exposes status bits saying + * which Secondary Interrupt Handler (SIH) stage is raising an interrupt. + * SIH modules are more traditional IRQ components, which support per-IRQ + * enable/disable and trigger controls; they do most of the work. + * + * These chips are designed to support IRQ handling from two different + * I2C masters. Each has a dedicated IRQ line, and dedicated IRQ status + * and mask registers in the PIH and SIH modules. + * + * We set up IRQs starting at a platform-specified base, always starting + * with PIH and the SIH for PWR_INT and then usually adding GPIO: + * base + 0 .. base + 7 PIH + * base + 8 .. base + 15 SIH for PWR_INT + * base + 16 .. base + 33 SIH for GPIO + */ + +/* PIH register offsets */ +#define REG_PIH_ISR_P1 0x01 +#define REG_PIH_ISR_P2 0x02 +#define REG_PIH_SIR 0x03 /* for testing */ + + +/* Linux could (eventually) use either IRQ line */ +static int irq_line; + +struct sih { + char name[8]; + u8 module; /* module id */ + u8 control_offset; /* for SIH_CTRL */ + bool set_cor; + + u8 bits; /* valid in isr/imr */ + u8 bytes_ixr; /* bytelen of ISR/IMR/SIR */ + + u8 edr_offset; + u8 bytes_edr; /* bytelen of EDR */ + + u8 irq_lines; /* number of supported irq lines */ + + /* SIR ignored -- set interrupt, for testing only */ + struct irq_data { + u8 isr_offset; + u8 imr_offset; + } mask[2]; + /* + 2 bytes padding */ +}; + +static const struct sih *sih_modules; +static int nr_sih_modules; + +#define SIH_INITIALIZER(modname, nbits) \ + .module = TWL4030_MODULE_ ## modname, \ + .control_offset = TWL4030_ ## modname ## _SIH_CTRL, \ + .bits = nbits, \ + .bytes_ixr = DIV_ROUND_UP(nbits, 8), \ + .edr_offset = TWL4030_ ## modname ## _EDR, \ + .bytes_edr = DIV_ROUND_UP((2*(nbits)), 8), \ + .irq_lines = 2, \ + .mask = { { \ + .isr_offset = TWL4030_ ## modname ## _ISR1, \ + .imr_offset = TWL4030_ ## modname ## _IMR1, \ + }, \ + { \ + .isr_offset = TWL4030_ ## modname ## _ISR2, \ + .imr_offset = TWL4030_ ## modname ## _IMR2, \ + }, }, + +/* register naming policies are inconsistent ... */ +#define TWL4030_INT_PWR_EDR TWL4030_INT_PWR_EDR1 +#define TWL4030_MODULE_KEYPAD_KEYP TWL4030_MODULE_KEYPAD +#define TWL4030_MODULE_INT_PWR TWL4030_MODULE_INT + + +/* Order in this table matches order in PIH_ISR. That is, + * BIT(n) in PIH_ISR is sih_modules[n]. + */ +/* sih_modules_twl4030 is used both in twl4030 and twl5030 */ +static const struct sih sih_modules_twl4030[6] = { + [0] = { + .name = "gpio", + .module = TWL4030_MODULE_GPIO, + .control_offset = REG_GPIO_SIH_CTRL, + .set_cor = true, + .bits = TWL4030_GPIO_MAX, + .bytes_ixr = 3, + /* Note: *all* of these IRQs default to no-trigger */ + .edr_offset = REG_GPIO_EDR1, + .bytes_edr = 5, + .irq_lines = 2, + .mask = { { + .isr_offset = REG_GPIO_ISR1A, + .imr_offset = REG_GPIO_IMR1A, + }, { + .isr_offset = REG_GPIO_ISR1B, + .imr_offset = REG_GPIO_IMR1B, + }, }, + }, + [1] = { + .name = "keypad", + .set_cor = true, + SIH_INITIALIZER(KEYPAD_KEYP, 4) + }, + [2] = { + .name = "bci", + .module = TWL4030_MODULE_INTERRUPTS, + .control_offset = TWL4030_INTERRUPTS_BCISIHCTRL, + .bits = 12, + .bytes_ixr = 2, + .edr_offset = TWL4030_INTERRUPTS_BCIEDR1, + /* Note: most of these IRQs default to no-trigger */ + .bytes_edr = 3, + .irq_lines = 2, + .mask = { { + .isr_offset = TWL4030_INTERRUPTS_BCIISR1A, + .imr_offset = TWL4030_INTERRUPTS_BCIIMR1A, + }, { + .isr_offset = TWL4030_INTERRUPTS_BCIISR1B, + .imr_offset = TWL4030_INTERRUPTS_BCIIMR1B, + }, }, + }, + [3] = { + .name = "madc", + SIH_INITIALIZER(MADC, 4) + }, + [4] = { + /* USB doesn't use the same SIH organization */ + .name = "usb", + }, + [5] = { + .name = "power", + .set_cor = true, + SIH_INITIALIZER(INT_PWR, 8) + }, + /* there are no SIH modules #6 or #7 ... */ +}; + +static const struct sih sih_modules_twl5031[8] = { + [0] = { + .name = "gpio", + .module = TWL4030_MODULE_GPIO, + .control_offset = REG_GPIO_SIH_CTRL, + .set_cor = true, + .bits = TWL4030_GPIO_MAX, + .bytes_ixr = 3, + /* Note: *all* of these IRQs default to no-trigger */ + .edr_offset = REG_GPIO_EDR1, + .bytes_edr = 5, + .irq_lines = 2, + .mask = { { + .isr_offset = REG_GPIO_ISR1A, + .imr_offset = REG_GPIO_IMR1A, + }, { + .isr_offset = REG_GPIO_ISR1B, + .imr_offset = REG_GPIO_IMR1B, + }, }, + }, + [1] = { + .name = "keypad", + .set_cor = true, + SIH_INITIALIZER(KEYPAD_KEYP, 4) + }, + [2] = { + .name = "bci", + .module = TWL5031_MODULE_INTERRUPTS, + .control_offset = TWL5031_INTERRUPTS_BCISIHCTRL, + .bits = 7, + .bytes_ixr = 1, + .edr_offset = TWL5031_INTERRUPTS_BCIEDR1, + /* Note: most of these IRQs default to no-trigger */ + .bytes_edr = 2, + .irq_lines = 2, + .mask = { { + .isr_offset = TWL5031_INTERRUPTS_BCIISR1, + .imr_offset = TWL5031_INTERRUPTS_BCIIMR1, + }, { + .isr_offset = TWL5031_INTERRUPTS_BCIISR2, + .imr_offset = TWL5031_INTERRUPTS_BCIIMR2, + }, }, + }, + [3] = { + .name = "madc", + SIH_INITIALIZER(MADC, 4) + }, + [4] = { + /* USB doesn't use the same SIH organization */ + .name = "usb", + }, + [5] = { + .name = "power", + .set_cor = true, + SIH_INITIALIZER(INT_PWR, 8) + }, + [6] = { + /* + * ACI doesn't use the same SIH organization. + * For example, it supports only one interrupt line + */ + .name = "aci", + .module = TWL5031_MODULE_ACCESSORY, + .bits = 9, + .bytes_ixr = 2, + .irq_lines = 1, + .mask = { { + .isr_offset = TWL5031_ACIIDR_LSB, + .imr_offset = TWL5031_ACIIMR_LSB, + }, }, + + }, + [7] = { + /* Accessory */ + .name = "acc", + .module = TWL5031_MODULE_ACCESSORY, + .control_offset = TWL5031_ACCSIHCTRL, + .bits = 2, + .bytes_ixr = 1, + .edr_offset = TWL5031_ACCEDR1, + /* Note: most of these IRQs default to no-trigger */ + .bytes_edr = 1, + .irq_lines = 2, + .mask = { { + .isr_offset = TWL5031_ACCISR1, + .imr_offset = TWL5031_ACCIMR1, + }, { + .isr_offset = TWL5031_ACCISR2, + .imr_offset = TWL5031_ACCIMR2, + }, }, + }, +}; + +#undef TWL4030_MODULE_KEYPAD_KEYP +#undef TWL4030_MODULE_INT_PWR +#undef TWL4030_INT_PWR_EDR + +/*----------------------------------------------------------------------*/ + +static unsigned twl4030_irq_base; + +static struct completion irq_event; + +/* + * This thread processes interrupts reported by the Primary Interrupt Handler. + */ +static int twl4030_irq_thread(void *data) +{ + long irq = (long)data; + static unsigned i2c_errors; + static const unsigned max_i2c_errors = 100; + + + current->flags |= PF_NOFREEZE; + + while (!kthread_should_stop()) { + int ret; + int module_irq; + u8 pih_isr; + + /* Wait for IRQ, then read PIH irq status (also blocking) */ + wait_for_completion_interruptible(&irq_event); + + ret = twl_i2c_read_u8(TWL4030_MODULE_PIH, &pih_isr, + REG_PIH_ISR_P1); + if (ret) { + pr_warning("twl4030: I2C error %d reading PIH ISR\n", + ret); + if (++i2c_errors >= max_i2c_errors) { + printk(KERN_ERR "Maximum I2C error count" + " exceeded. Terminating %s.\n", + __func__); + break; + } + complete(&irq_event); + continue; + } + + /* these handlers deal with the relevant SIH irq status */ + local_irq_disable(); + for (module_irq = twl4030_irq_base; + pih_isr; + pih_isr >>= 1, module_irq++) { + if (pih_isr & 0x1) { + struct irq_desc *d = irq_to_desc(module_irq); + + if (!d) { + pr_err("twl4030: Invalid SIH IRQ: %d\n", + module_irq); + return -EINVAL; + } + + /* These can't be masked ... always warn + * if we get any surprises. + */ + if (d->status & IRQ_DISABLED) + note_interrupt(module_irq, d, + IRQ_NONE); + else + d->handle_irq(module_irq, d); + } + } + local_irq_enable(); + + enable_irq(irq); + } + + return 0; +} + +/* + * handle_twl4030_pih() is the desc->handle method for the twl4030 interrupt. + * This is a chained interrupt, so there is no desc->action method for it. + * Now we need to query the interrupt controller in the twl4030 to determine + * which module is generating the interrupt request. However, we can't do i2c + * transactions in interrupt context, so we must defer that work to a kernel + * thread. All we do here is acknowledge and mask the interrupt and wakeup + * the kernel thread. + */ +static irqreturn_t handle_twl4030_pih(int irq, void *devid) +{ + /* Acknowledge, clear *AND* mask the interrupt... */ + disable_irq_nosync(irq); + complete(devid); + return IRQ_HANDLED; +} +/*----------------------------------------------------------------------*/ + +/* + * twl4030_init_sih_modules() ... start from a known state where no + * IRQs will be coming in, and where we can quickly enable them then + * handle them as they arrive. Mask all IRQs: maybe init SIH_CTRL. + * + * NOTE: we don't touch EDR registers here; they stay with hardware + * defaults or whatever the last value was. Note that when both EDR + * bits for an IRQ are clear, that's as if its IMR bit is set... + */ +static int twl4030_init_sih_modules(unsigned line) +{ + const struct sih *sih; + u8 buf[4]; + int i; + int status; + + /* line 0 == int1_n signal; line 1 == int2_n signal */ + if (line > 1) + return -EINVAL; + + irq_line = line; + + /* disable all interrupts on our line */ + memset(buf, 0xff, sizeof buf); + sih = sih_modules; + for (i = 0; i < nr_sih_modules; i++, sih++) { + + /* skip USB -- it's funky */ + if (!sih->bytes_ixr) + continue; + + /* Not all the SIH modules support multiple interrupt lines */ + if (sih->irq_lines <= line) + continue; + + status = twl_i2c_write(sih->module, buf, + sih->mask[line].imr_offset, sih->bytes_ixr); + if (status < 0) + pr_err("twl4030: err %d initializing %s %s\n", + status, sih->name, "IMR"); + + /* Maybe disable "exclusive" mode; buffer second pending irq; + * set Clear-On-Read (COR) bit. + * + * NOTE that sometimes COR polarity is documented as being + * inverted: for MADC and BCI, COR=1 means "clear on write". + * And for PWR_INT it's not documented... + */ + if (sih->set_cor) { + status = twl_i2c_write_u8(sih->module, + TWL4030_SIH_CTRL_COR_MASK, + sih->control_offset); + if (status < 0) + pr_err("twl4030: err %d initializing %s %s\n", + status, sih->name, "SIH_CTRL"); + } + } + + sih = sih_modules; + for (i = 0; i < nr_sih_modules; i++, sih++) { + u8 rxbuf[4]; + int j; + + /* skip USB */ + if (!sih->bytes_ixr) + continue; + + /* Not all the SIH modules support multiple interrupt lines */ + if (sih->irq_lines <= line) + continue; + + /* Clear pending interrupt status. Either the read was + * enough, or we need to write those bits. Repeat, in + * case an IRQ is pending (PENDDIS=0) ... that's not + * uncommon with PWR_INT.PWRON. + */ + for (j = 0; j < 2; j++) { + status = twl_i2c_read(sih->module, rxbuf, + sih->mask[line].isr_offset, sih->bytes_ixr); + if (status < 0) + pr_err("twl4030: err %d initializing %s %s\n", + status, sih->name, "ISR"); + + if (!sih->set_cor) + status = twl_i2c_write(sih->module, buf, + sih->mask[line].isr_offset, + sih->bytes_ixr); + /* else COR=1 means read sufficed. + * (for most SIH modules...) + */ + } + } + + return 0; +} + +static inline void activate_irq(int irq) +{ +#ifdef CONFIG_ARM + /* ARM requires an extra step to clear IRQ_NOREQUEST, which it + * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE. + */ + set_irq_flags(irq, IRQF_VALID); +#else + /* same effect on other architectures */ + set_irq_noprobe(irq); +#endif +} + +/*----------------------------------------------------------------------*/ + +static DEFINE_SPINLOCK(sih_agent_lock); + +static struct workqueue_struct *wq; + +struct sih_agent { + int irq_base; + const struct sih *sih; + + u32 imr; + bool imr_change_pending; + struct work_struct mask_work; + + u32 edge_change; + struct work_struct edge_work; +}; + +static void twl4030_sih_do_mask(struct work_struct *work) +{ + struct sih_agent *agent; + const struct sih *sih; + union { + u8 bytes[4]; + u32 word; + } imr; + int status; + + agent = container_of(work, struct sih_agent, mask_work); + + /* see what work we have */ + spin_lock_irq(&sih_agent_lock); + if (agent->imr_change_pending) { + sih = agent->sih; + /* byte[0] gets overwritten as we write ... */ + imr.word = cpu_to_le32(agent->imr << 8); + agent->imr_change_pending = false; + } else + sih = NULL; + spin_unlock_irq(&sih_agent_lock); + if (!sih) + return; + + /* write the whole mask ... simpler than subsetting it */ + status = twl_i2c_write(sih->module, imr.bytes, + sih->mask[irq_line].imr_offset, sih->bytes_ixr); + if (status) + pr_err("twl4030: %s, %s --> %d\n", __func__, + "write", status); +} + +static void twl4030_sih_do_edge(struct work_struct *work) +{ + struct sih_agent *agent; + const struct sih *sih; + u8 bytes[6]; + u32 edge_change; + int status; + + agent = container_of(work, struct sih_agent, edge_work); + + /* see what work we have */ + spin_lock_irq(&sih_agent_lock); + edge_change = agent->edge_change; + agent->edge_change = 0; + sih = edge_change ? agent->sih : NULL; + spin_unlock_irq(&sih_agent_lock); + if (!sih) + return; + + /* Read, reserving first byte for write scratch. Yes, this + * could be cached for some speedup ... but be careful about + * any processor on the other IRQ line, EDR registers are + * shared. + */ + status = twl_i2c_read(sih->module, bytes + 1, + sih->edr_offset, sih->bytes_edr); + if (status) { + pr_err("twl4030: %s, %s --> %d\n", __func__, + "read", status); + return; + } + + /* Modify only the bits we know must change */ + while (edge_change) { + int i = fls(edge_change) - 1; + struct irq_desc *d = irq_to_desc(i + agent->irq_base); + int byte = 1 + (i >> 2); + int off = (i & 0x3) * 2; + + if (!d) { + pr_err("twl4030: Invalid IRQ: %d\n", + i + agent->irq_base); + return; + } + + bytes[byte] &= ~(0x03 << off); + + raw_spin_lock_irq(&d->lock); + if (d->status & IRQ_TYPE_EDGE_RISING) + bytes[byte] |= BIT(off + 1); + if (d->status & IRQ_TYPE_EDGE_FALLING) + bytes[byte] |= BIT(off + 0); + raw_spin_unlock_irq(&d->lock); + + edge_change &= ~BIT(i); + } + + /* Write */ + status = twl_i2c_write(sih->module, bytes, + sih->edr_offset, sih->bytes_edr); + if (status) + pr_err("twl4030: %s, %s --> %d\n", __func__, + "write", status); +} + +/*----------------------------------------------------------------------*/ + +/* + * All irq_chip methods get issued from code holding irq_desc[irq].lock, + * which can't perform the underlying I2C operations (because they sleep). + * So we must hand them off to a thread (workqueue) and cope with asynch + * completion, potentially including some re-ordering, of these requests. + */ + +static void twl4030_sih_mask(unsigned irq) +{ + struct sih_agent *sih = get_irq_chip_data(irq); + unsigned long flags; + + spin_lock_irqsave(&sih_agent_lock, flags); + sih->imr |= BIT(irq - sih->irq_base); + sih->imr_change_pending = true; + queue_work(wq, &sih->mask_work); + spin_unlock_irqrestore(&sih_agent_lock, flags); +} + +static void twl4030_sih_unmask(unsigned irq) +{ + struct sih_agent *sih = get_irq_chip_data(irq); + unsigned long flags; + + spin_lock_irqsave(&sih_agent_lock, flags); + sih->imr &= ~BIT(irq - sih->irq_base); + sih->imr_change_pending = true; + queue_work(wq, &sih->mask_work); + spin_unlock_irqrestore(&sih_agent_lock, flags); +} + +static int twl4030_sih_set_type(unsigned irq, unsigned trigger) +{ + struct sih_agent *sih = get_irq_chip_data(irq); + struct irq_desc *desc = irq_to_desc(irq); + unsigned long flags; + + if (!desc) { + pr_err("twl4030: Invalid IRQ: %d\n", irq); + return -EINVAL; + } + + if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + return -EINVAL; + + spin_lock_irqsave(&sih_agent_lock, flags); + if ((desc->status & IRQ_TYPE_SENSE_MASK) != trigger) { + desc->status &= ~IRQ_TYPE_SENSE_MASK; + desc->status |= trigger; + sih->edge_change |= BIT(irq - sih->irq_base); + queue_work(wq, &sih->edge_work); + } + spin_unlock_irqrestore(&sih_agent_lock, flags); + return 0; +} + +static struct irq_chip twl4030_sih_irq_chip = { + .name = "twl4030", + .mask = twl4030_sih_mask, + .unmask = twl4030_sih_unmask, + .set_type = twl4030_sih_set_type, +}; + +/*----------------------------------------------------------------------*/ + +static inline int sih_read_isr(const struct sih *sih) +{ + int status; + union { + u8 bytes[4]; + u32 word; + } isr; + + /* FIXME need retry-on-error ... */ + + isr.word = 0; + status = twl_i2c_read(sih->module, isr.bytes, + sih->mask[irq_line].isr_offset, sih->bytes_ixr); + + return (status < 0) ? status : le32_to_cpu(isr.word); +} + +/* + * Generic handler for SIH interrupts ... we "know" this is called + * in task context, with IRQs enabled. + */ +static void handle_twl4030_sih(unsigned irq, struct irq_desc *desc) +{ + struct sih_agent *agent = get_irq_data(irq); + const struct sih *sih = agent->sih; + int isr; + + /* reading ISR acks the IRQs, using clear-on-read mode */ + local_irq_enable(); + isr = sih_read_isr(sih); + local_irq_disable(); + + if (isr < 0) { + pr_err("twl4030: %s SIH, read ISR error %d\n", + sih->name, isr); + /* REVISIT: recover; eventually mask it all, etc */ + return; + } + + while (isr) { + irq = fls(isr); + irq--; + isr &= ~BIT(irq); + + if (irq < sih->bits) + generic_handle_irq(agent->irq_base + irq); + else + pr_err("twl4030: %s SIH, invalid ISR bit %d\n", + sih->name, irq); + } +} + +static unsigned twl4030_irq_next; + +/* returns the first IRQ used by this SIH bank, + * or negative errno + */ +int twl4030_sih_setup(int module) +{ + int sih_mod; + const struct sih *sih = NULL; + struct sih_agent *agent; + int i, irq; + int status = -EINVAL; + unsigned irq_base = twl4030_irq_next; + + /* only support modules with standard clear-on-read for now */ + for (sih_mod = 0, sih = sih_modules; + sih_mod < nr_sih_modules; + sih_mod++, sih++) { + if (sih->module == module && sih->set_cor) { + if (!WARN((irq_base + sih->bits) > NR_IRQS, + "irq %d for %s too big\n", + irq_base + sih->bits, + sih->name)) + status = 0; + break; + } + } + if (status < 0) + return status; + + agent = kzalloc(sizeof *agent, GFP_KERNEL); + if (!agent) + return -ENOMEM; + + status = 0; + + agent->irq_base = irq_base; + agent->sih = sih; + agent->imr = ~0; + INIT_WORK(&agent->mask_work, twl4030_sih_do_mask); + INIT_WORK(&agent->edge_work, twl4030_sih_do_edge); + + for (i = 0; i < sih->bits; i++) { + irq = irq_base + i; + + set_irq_chip_and_handler(irq, &twl4030_sih_irq_chip, + handle_edge_irq); + set_irq_chip_data(irq, agent); + activate_irq(irq); + } + + status = irq_base; + twl4030_irq_next += i; + + /* replace generic PIH handler (handle_simple_irq) */ + irq = sih_mod + twl4030_irq_base; + set_irq_data(irq, agent); + set_irq_chained_handler(irq, handle_twl4030_sih); + + pr_info("twl4030: %s (irq %d) chaining IRQs %d..%d\n", sih->name, + irq, irq_base, twl4030_irq_next - 1); + + return status; +} + +/* FIXME need a call to reverse twl4030_sih_setup() ... */ + + +/*----------------------------------------------------------------------*/ + +/* FIXME pass in which interrupt line we'll use ... */ +#define twl_irq_line 0 + +int twl4030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end) +{ + static struct irq_chip twl4030_irq_chip; + + int status; + int i; + struct task_struct *task; + + /* + * Mask and clear all TWL4030 interrupts since initially we do + * not have any TWL4030 module interrupt handlers present + */ + status = twl4030_init_sih_modules(twl_irq_line); + if (status < 0) + return status; + + wq = create_singlethread_workqueue("twl4030-irqchip"); + if (!wq) { + pr_err("twl4030: workqueue FAIL\n"); + return -ESRCH; + } + + twl4030_irq_base = irq_base; + + /* install an irq handler for each of the SIH modules; + * clone dummy irq_chip since PIH can't *do* anything + */ + twl4030_irq_chip = dummy_irq_chip; + twl4030_irq_chip.name = "twl4030"; + + twl4030_sih_irq_chip.ack = dummy_irq_chip.ack; + + for (i = irq_base; i < irq_end; i++) { + set_irq_chip_and_handler(i, &twl4030_irq_chip, + handle_simple_irq); + activate_irq(i); + } + twl4030_irq_next = i; + pr_info("twl4030: %s (irq %d) chaining IRQs %d..%d\n", "PIH", + irq_num, irq_base, twl4030_irq_next - 1); + + /* ... and the PWR_INT module ... */ + status = twl4030_sih_setup(TWL4030_MODULE_INT); + if (status < 0) { + pr_err("twl4030: sih_setup PWR INT --> %d\n", status); + goto fail; + } + + /* install an irq handler to demultiplex the TWL4030 interrupt */ + + + init_completion(&irq_event); + + status = request_irq(irq_num, handle_twl4030_pih, IRQF_DISABLED, + "TWL4030-PIH", &irq_event); + if (status < 0) { + pr_err("twl4030: could not claim irq%d: %d\n", irq_num, status); + goto fail_rqirq; + } + + task = kthread_run(twl4030_irq_thread, (void *)(long)irq_num, + "twl4030-irq"); + if (IS_ERR(task)) { + pr_err("twl4030: could not create irq %d thread!\n", irq_num); + status = PTR_ERR(task); + goto fail_kthread; + } + return status; +fail_kthread: + free_irq(irq_num, &irq_event); +fail_rqirq: + /* clean up twl4030_sih_setup */ +fail: + for (i = irq_base; i < irq_end; i++) + set_irq_chip_and_handler(i, NULL, NULL); + destroy_workqueue(wq); + wq = NULL; + return status; +} + +int twl4030_exit_irq(void) +{ + /* FIXME undo twl_init_irq() */ + if (twl4030_irq_base) { + pr_err("twl4030: can't yet clean up IRQs?\n"); + return -ENOSYS; + } + return 0; +} + +int twl4030_init_chip_irq(const char *chip) +{ + if (!strcmp(chip, "twl5031")) { + sih_modules = sih_modules_twl5031; + nr_sih_modules = ARRAY_SIZE(sih_modules_twl5031); + } else { + sih_modules = sih_modules_twl4030; + nr_sih_modules = ARRAY_SIZE(sih_modules_twl4030); + } + + return 0; +} diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c new file mode 100644 index 00000000000..0815292fdaf --- /dev/null +++ b/drivers/mfd/twl4030-power.c @@ -0,0 +1,516 @@ +/* + * linux/drivers/i2c/chips/twl4030-power.c + * + * Handle TWL4030 Power initialization + * + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2006 Texas Instruments, Inc + * + * Written by Kalle Jokiniemi + * Peter De Schrijver <peter.de-schrijver@nokia.com> + * Several fixes by Amit Kucheria <amit.kucheria@verdurent.com> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/i2c/twl.h> +#include <linux/platform_device.h> + +#include <asm/mach-types.h> + +static u8 twl4030_start_script_address = 0x2b; + +#define PWR_P1_SW_EVENTS 0x10 +#define PWR_DEVOFF (1<<0) + +#define PHY_TO_OFF_PM_MASTER(p) (p - 0x36) +#define PHY_TO_OFF_PM_RECEIVER(p) (p - 0x5b) + +/* resource - hfclk */ +#define R_HFCLKOUT_DEV_GRP PHY_TO_OFF_PM_RECEIVER(0xe6) + +/* PM events */ +#define R_P1_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x46) +#define R_P2_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x47) +#define R_P3_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x48) +#define R_CFG_P1_TRANSITION PHY_TO_OFF_PM_MASTER(0x36) +#define R_CFG_P2_TRANSITION PHY_TO_OFF_PM_MASTER(0x37) +#define R_CFG_P3_TRANSITION PHY_TO_OFF_PM_MASTER(0x38) + +#define LVL_WAKEUP 0x08 + +#define ENABLE_WARMRESET (1<<4) + +#define END_OF_SCRIPT 0x3f + +#define R_SEQ_ADD_A2S PHY_TO_OFF_PM_MASTER(0x55) +#define R_SEQ_ADD_S2A12 PHY_TO_OFF_PM_MASTER(0x56) +#define R_SEQ_ADD_S2A3 PHY_TO_OFF_PM_MASTER(0x57) +#define R_SEQ_ADD_WARM PHY_TO_OFF_PM_MASTER(0x58) +#define R_MEMORY_ADDRESS PHY_TO_OFF_PM_MASTER(0x59) +#define R_MEMORY_DATA PHY_TO_OFF_PM_MASTER(0x5a) + +#define R_PROTECT_KEY 0x0E +#define R_KEY_1 0xC0 +#define R_KEY_2 0x0C + +/* resource configuration registers + <RESOURCE>_DEV_GRP at address 'n+0' + <RESOURCE>_TYPE at address 'n+1' + <RESOURCE>_REMAP at address 'n+2' + <RESOURCE>_DEDICATED at address 'n+3' +*/ +#define DEV_GRP_OFFSET 0 +#define TYPE_OFFSET 1 +#define REMAP_OFFSET 2 +#define DEDICATED_OFFSET 3 + +/* Bit positions in the registers */ + +/* <RESOURCE>_DEV_GRP */ +#define DEV_GRP_SHIFT 5 +#define DEV_GRP_MASK (7 << DEV_GRP_SHIFT) + +/* <RESOURCE>_TYPE */ +#define TYPE_SHIFT 0 +#define TYPE_MASK (7 << TYPE_SHIFT) +#define TYPE2_SHIFT 3 +#define TYPE2_MASK (3 << TYPE2_SHIFT) + +/* <RESOURCE>_REMAP */ +#define SLEEP_STATE_SHIFT 0 +#define SLEEP_STATE_MASK (0xf << SLEEP_STATE_SHIFT) +#define OFF_STATE_SHIFT 4 +#define OFF_STATE_MASK (0xf << OFF_STATE_SHIFT) + +static u8 res_config_addrs[] = { + [RES_VAUX1] = 0x17, + [RES_VAUX2] = 0x1b, + [RES_VAUX3] = 0x1f, + [RES_VAUX4] = 0x23, + [RES_VMMC1] = 0x27, + [RES_VMMC2] = 0x2b, + [RES_VPLL1] = 0x2f, + [RES_VPLL2] = 0x33, + [RES_VSIM] = 0x37, + [RES_VDAC] = 0x3b, + [RES_VINTANA1] = 0x3f, + [RES_VINTANA2] = 0x43, + [RES_VINTDIG] = 0x47, + [RES_VIO] = 0x4b, + [RES_VDD1] = 0x55, + [RES_VDD2] = 0x63, + [RES_VUSB_1V5] = 0x71, + [RES_VUSB_1V8] = 0x74, + [RES_VUSB_3V1] = 0x77, + [RES_VUSBCP] = 0x7a, + [RES_REGEN] = 0x7f, + [RES_NRES_PWRON] = 0x82, + [RES_CLKEN] = 0x85, + [RES_SYSEN] = 0x88, + [RES_HFCLKOUT] = 0x8b, + [RES_32KCLKOUT] = 0x8e, + [RES_RESET] = 0x91, + [RES_Main_Ref] = 0x94, +}; + +static int __init twl4030_write_script_byte(u8 address, u8 byte) +{ + int err; + + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_MEMORY_ADDRESS); + if (err) + goto out; + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, byte, + R_MEMORY_DATA); +out: + return err; +} + +static int __init twl4030_write_script_ins(u8 address, u16 pmb_message, + u8 delay, u8 next) +{ + int err; + + address *= 4; + err = twl4030_write_script_byte(address++, pmb_message >> 8); + if (err) + goto out; + err = twl4030_write_script_byte(address++, pmb_message & 0xff); + if (err) + goto out; + err = twl4030_write_script_byte(address++, delay); + if (err) + goto out; + err = twl4030_write_script_byte(address++, next); +out: + return err; +} + +static int __init twl4030_write_script(u8 address, struct twl4030_ins *script, + int len) +{ + int err; + + for (; len; len--, address++, script++) { + if (len == 1) { + err = twl4030_write_script_ins(address, + script->pmb_message, + script->delay, + END_OF_SCRIPT); + if (err) + break; + } else { + err = twl4030_write_script_ins(address, + script->pmb_message, + script->delay, + address + 1); + if (err) + break; + } + } + return err; +} + +static int __init twl4030_config_wakeup3_sequence(u8 address) +{ + int err; + u8 data; + + /* Set SLEEP to ACTIVE SEQ address for P3 */ + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_S2A3); + if (err) + goto out; + + /* P3 LVL_WAKEUP should be on LEVEL */ + err = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_P3_SW_EVENTS); + if (err) + goto out; + data |= LVL_WAKEUP; + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data, + R_P3_SW_EVENTS); +out: + if (err) + pr_err("TWL4030 wakeup sequence for P3 config error\n"); + return err; +} + +static int __init twl4030_config_wakeup12_sequence(u8 address) +{ + int err = 0; + u8 data; + + /* Set SLEEP to ACTIVE SEQ address for P1 and P2 */ + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_S2A12); + if (err) + goto out; + + /* P1/P2 LVL_WAKEUP should be on LEVEL */ + err = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_P1_SW_EVENTS); + if (err) + goto out; + + data |= LVL_WAKEUP; + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data, + R_P1_SW_EVENTS); + if (err) + goto out; + + err = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_P2_SW_EVENTS); + if (err) + goto out; + + data |= LVL_WAKEUP; + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data, + R_P2_SW_EVENTS); + if (err) + goto out; + + if (machine_is_omap_3430sdp() || machine_is_omap_ldp()) { + /* Disabling AC charger effect on sleep-active transitions */ + err = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_CFG_P1_TRANSITION); + if (err) + goto out; + data &= ~(1<<1); + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data , + R_CFG_P1_TRANSITION); + if (err) + goto out; + } + +out: + if (err) + pr_err("TWL4030 wakeup sequence for P1 and P2" \ + "config error\n"); + return err; +} + +static int __init twl4030_config_sleep_sequence(u8 address) +{ + int err; + + /* Set ACTIVE to SLEEP SEQ address in T2 memory*/ + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_A2S); + + if (err) + pr_err("TWL4030 sleep sequence config error\n"); + + return err; +} + +static int __init twl4030_config_warmreset_sequence(u8 address) +{ + int err; + u8 rd_data; + + /* Set WARM RESET SEQ address for P1 */ + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_WARM); + if (err) + goto out; + + /* P1/P2/P3 enable WARMRESET */ + err = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data, + R_P1_SW_EVENTS); + if (err) + goto out; + + rd_data |= ENABLE_WARMRESET; + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data, + R_P1_SW_EVENTS); + if (err) + goto out; + + err = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data, + R_P2_SW_EVENTS); + if (err) + goto out; + + rd_data |= ENABLE_WARMRESET; + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data, + R_P2_SW_EVENTS); + if (err) + goto out; + + err = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data, + R_P3_SW_EVENTS); + if (err) + goto out; + + rd_data |= ENABLE_WARMRESET; + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data, + R_P3_SW_EVENTS); +out: + if (err) + pr_err("TWL4030 warmreset seq config error\n"); + return err; +} + +static int __init twl4030_configure_resource(struct twl4030_resconfig *rconfig) +{ + int rconfig_addr; + int err; + u8 type; + u8 grp; + u8 remap; + + if (rconfig->resource > TOTAL_RESOURCES) { + pr_err("TWL4030 Resource %d does not exist\n", + rconfig->resource); + return -EINVAL; + } + + rconfig_addr = res_config_addrs[rconfig->resource]; + + /* Set resource group */ + err = twl_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, &grp, + rconfig_addr + DEV_GRP_OFFSET); + if (err) { + pr_err("TWL4030 Resource %d group could not be read\n", + rconfig->resource); + return err; + } + + if (rconfig->devgroup != TWL4030_RESCONFIG_UNDEF) { + grp &= ~DEV_GRP_MASK; + grp |= rconfig->devgroup << DEV_GRP_SHIFT; + err = twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + grp, rconfig_addr + DEV_GRP_OFFSET); + if (err < 0) { + pr_err("TWL4030 failed to program devgroup\n"); + return err; + } + } + + /* Set resource types */ + err = twl_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, &type, + rconfig_addr + TYPE_OFFSET); + if (err < 0) { + pr_err("TWL4030 Resource %d type could not be read\n", + rconfig->resource); + return err; + } + + if (rconfig->type != TWL4030_RESCONFIG_UNDEF) { + type &= ~TYPE_MASK; + type |= rconfig->type << TYPE_SHIFT; + } + + if (rconfig->type2 != TWL4030_RESCONFIG_UNDEF) { + type &= ~TYPE2_MASK; + type |= rconfig->type2 << TYPE2_SHIFT; + } + + err = twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + type, rconfig_addr + TYPE_OFFSET); + if (err < 0) { + pr_err("TWL4030 failed to program resource type\n"); + return err; + } + + /* Set remap states */ + err = twl_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, &remap, + rconfig_addr + REMAP_OFFSET); + if (err < 0) { + pr_err("TWL4030 Resource %d remap could not be read\n", + rconfig->resource); + return err; + } + + if (rconfig->remap_off != TWL4030_RESCONFIG_UNDEF) { + remap &= ~OFF_STATE_MASK; + remap |= rconfig->remap_off << OFF_STATE_SHIFT; + } + + if (rconfig->remap_sleep != TWL4030_RESCONFIG_UNDEF) { + remap &= ~SLEEP_STATE_MASK; + remap |= rconfig->remap_off << SLEEP_STATE_SHIFT; + } + + err = twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + remap, + rconfig_addr + REMAP_OFFSET); + if (err < 0) { + pr_err("TWL4030 failed to program remap\n"); + return err; + } + + return 0; +} + +static int __init load_twl4030_script(struct twl4030_script *tscript, + u8 address) +{ + int err; + static int order; + + /* Make sure the script isn't going beyond last valid address (0x3f) */ + if ((address + tscript->size) > END_OF_SCRIPT) { + pr_err("TWL4030 scripts too big error\n"); + return -EINVAL; + } + + err = twl4030_write_script(address, tscript->script, tscript->size); + if (err) + goto out; + + if (tscript->flags & TWL4030_WRST_SCRIPT) { + err = twl4030_config_warmreset_sequence(address); + if (err) + goto out; + } + if (tscript->flags & TWL4030_WAKEUP12_SCRIPT) { + err = twl4030_config_wakeup12_sequence(address); + if (err) + goto out; + order = 1; + } + if (tscript->flags & TWL4030_WAKEUP3_SCRIPT) { + err = twl4030_config_wakeup3_sequence(address); + if (err) + goto out; + } + if (tscript->flags & TWL4030_SLEEP_SCRIPT) + if (order) + pr_warning("TWL4030: Bad order of scripts (sleep "\ + "script before wakeup) Leads to boot"\ + "failure on some boards\n"); + err = twl4030_config_sleep_sequence(address); +out: + return err; +} + +void __init twl4030_power_init(struct twl4030_power_data *twl4030_scripts) +{ + int err = 0; + int i; + struct twl4030_resconfig *resconfig; + u8 address = twl4030_start_script_address; + + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, R_KEY_1, + R_PROTECT_KEY); + if (err) + goto unlock; + + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, R_KEY_2, + R_PROTECT_KEY); + if (err) + goto unlock; + + for (i = 0; i < twl4030_scripts->num; i++) { + err = load_twl4030_script(twl4030_scripts->scripts[i], address); + if (err) + goto load; + address += twl4030_scripts->scripts[i]->size; + } + + resconfig = twl4030_scripts->resource_config; + if (resconfig) { + while (resconfig->resource) { + err = twl4030_configure_resource(resconfig); + if (err) + goto resource; + resconfig++; + + } + } + + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0, R_PROTECT_KEY); + if (err) + pr_err("TWL4030 Unable to relock registers\n"); + return; + +unlock: + if (err) + pr_err("TWL4030 Unable to unlock registers\n"); + return; +load: + if (err) + pr_err("TWL4030 failed to load scripts\n"); + return; +resource: + if (err) + pr_err("TWL4030 failed to configure resource\n"); + return; +} diff --git a/drivers/mfd/twl6030-irq.c b/drivers/mfd/twl6030-irq.c new file mode 100644 index 00000000000..10bf228ad62 --- /dev/null +++ b/drivers/mfd/twl6030-irq.c @@ -0,0 +1,299 @@ +/* + * twl6030-irq.c - TWL6030 irq support + * + * Copyright (C) 2005-2009 Texas Instruments, Inc. + * + * Modifications to defer interrupt handling to a kernel thread: + * Copyright (C) 2006 MontaVista Software, Inc. + * + * Based on tlv320aic23.c: + * Copyright (c) by Kai Svahn <kai.svahn@nokia.com> + * + * Code cleanup and modifications to IRQ handler. + * by syed khasim <x0khasim@ti.com> + * + * TWL6030 specific code and IRQ handling changes by + * Jagadeesh Bhaskar Pakaravoor <j-pakaravoor@ti.com> + * Balaji T K <balajitk@ti.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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kthread.h> +#include <linux/i2c/twl.h> + +/* + * TWL6030 (unlike its predecessors, which had two level interrupt handling) + * three interrupt registers INT_STS_A, INT_STS_B and INT_STS_C. + * It exposes status bits saying who has raised an interrupt. There are + * three mask registers that corresponds to these status registers, that + * enables/disables these interrupts. + * + * We set up IRQs starting at a platform-specified base. An interrupt map table, + * specifies mapping between interrupt number and the associated module. + * + */ + +static int twl6030_interrupt_mapping[24] = { + PWR_INTR_OFFSET, /* Bit 0 PWRON */ + PWR_INTR_OFFSET, /* Bit 1 RPWRON */ + PWR_INTR_OFFSET, /* Bit 2 BAT_VLOW */ + RTC_INTR_OFFSET, /* Bit 3 RTC_ALARM */ + RTC_INTR_OFFSET, /* Bit 4 RTC_PERIOD */ + HOTDIE_INTR_OFFSET, /* Bit 5 HOT_DIE */ + SMPSLDO_INTR_OFFSET, /* Bit 6 VXXX_SHORT */ + SMPSLDO_INTR_OFFSET, /* Bit 7 VMMC_SHORT */ + + SMPSLDO_INTR_OFFSET, /* Bit 8 VUSIM_SHORT */ + BATDETECT_INTR_OFFSET, /* Bit 9 BAT */ + SIMDETECT_INTR_OFFSET, /* Bit 10 SIM */ + MMCDETECT_INTR_OFFSET, /* Bit 11 MMC */ + RSV_INTR_OFFSET, /* Bit 12 Reserved */ + MADC_INTR_OFFSET, /* Bit 13 GPADC_RT_EOC */ + MADC_INTR_OFFSET, /* Bit 14 GPADC_SW_EOC */ + GASGAUGE_INTR_OFFSET, /* Bit 15 CC_AUTOCAL */ + + USBOTG_INTR_OFFSET, /* Bit 16 ID_WKUP */ + USBOTG_INTR_OFFSET, /* Bit 17 VBUS_WKUP */ + USBOTG_INTR_OFFSET, /* Bit 18 ID */ + USBOTG_INTR_OFFSET, /* Bit 19 VBUS */ + CHARGER_INTR_OFFSET, /* Bit 20 CHRG_CTRL */ + CHARGER_INTR_OFFSET, /* Bit 21 EXT_CHRG */ + CHARGER_INTR_OFFSET, /* Bit 22 INT_CHRG */ + RSV_INTR_OFFSET, /* Bit 23 Reserved */ +}; +/*----------------------------------------------------------------------*/ + +static unsigned twl6030_irq_base; + +static struct completion irq_event; + +/* + * This thread processes interrupts reported by the Primary Interrupt Handler. + */ +static int twl6030_irq_thread(void *data) +{ + long irq = (long)data; + static unsigned i2c_errors; + static const unsigned max_i2c_errors = 100; + int ret; + + current->flags |= PF_NOFREEZE; + + while (!kthread_should_stop()) { + int i; + union { + u8 bytes[4]; + u32 int_sts; + } sts; + + /* Wait for IRQ, then read PIH irq status (also blocking) */ + wait_for_completion_interruptible(&irq_event); + + /* read INT_STS_A, B and C in one shot using a burst read */ + ret = twl_i2c_read(TWL_MODULE_PIH, sts.bytes, + REG_INT_STS_A, 3); + if (ret) { + pr_warning("twl6030: I2C error %d reading PIH ISR\n", + ret); + if (++i2c_errors >= max_i2c_errors) { + printk(KERN_ERR "Maximum I2C error count" + " exceeded. Terminating %s.\n", + __func__); + break; + } + complete(&irq_event); + continue; + } + + + + sts.bytes[3] = 0; /* Only 24 bits are valid*/ + + for (i = 0; sts.int_sts; sts.int_sts >>= 1, i++) { + local_irq_disable(); + if (sts.int_sts & 0x1) { + int module_irq = twl6030_irq_base + + twl6030_interrupt_mapping[i]; + struct irq_desc *d = irq_to_desc(module_irq); + + if (!d) { + pr_err("twl6030: Invalid SIH IRQ: %d\n", + module_irq); + return -EINVAL; + } + + /* These can't be masked ... always warn + * if we get any surprises. + */ + if (d->status & IRQ_DISABLED) + note_interrupt(module_irq, d, + IRQ_NONE); + else + d->handle_irq(module_irq, d); + + } + local_irq_enable(); + } + ret = twl_i2c_write(TWL_MODULE_PIH, sts.bytes, + REG_INT_STS_A, 3); /* clear INT_STS_A */ + if (ret) + pr_warning("twl6030: I2C error in clearing PIH ISR\n"); + + enable_irq(irq); + } + + return 0; +} + +/* + * handle_twl6030_int() is the desc->handle method for the twl6030 interrupt. + * This is a chained interrupt, so there is no desc->action method for it. + * Now we need to query the interrupt controller in the twl6030 to determine + * which module is generating the interrupt request. However, we can't do i2c + * transactions in interrupt context, so we must defer that work to a kernel + * thread. All we do here is acknowledge and mask the interrupt and wakeup + * the kernel thread. + */ +static irqreturn_t handle_twl6030_pih(int irq, void *devid) +{ + disable_irq_nosync(irq); + complete(devid); + return IRQ_HANDLED; +} + +/*----------------------------------------------------------------------*/ + +static inline void activate_irq(int irq) +{ +#ifdef CONFIG_ARM + /* ARM requires an extra step to clear IRQ_NOREQUEST, which it + * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE. + */ + set_irq_flags(irq, IRQF_VALID); +#else + /* same effect on other architectures */ + set_irq_noprobe(irq); +#endif +} + +/*----------------------------------------------------------------------*/ + +static unsigned twl6030_irq_next; + +/*----------------------------------------------------------------------*/ +int twl6030_interrupt_unmask(u8 bit_mask, u8 offset) +{ + int ret; + u8 unmask_value; + ret = twl_i2c_read_u8(TWL_MODULE_PIH, &unmask_value, + REG_INT_STS_A + offset); + unmask_value &= (~(bit_mask)); + ret |= twl_i2c_write_u8(TWL_MODULE_PIH, unmask_value, + REG_INT_STS_A + offset); /* unmask INT_MSK_A/B/C */ + return ret; +} +EXPORT_SYMBOL(twl6030_interrupt_unmask); + +int twl6030_interrupt_mask(u8 bit_mask, u8 offset) +{ + int ret; + u8 mask_value; + ret = twl_i2c_read_u8(TWL_MODULE_PIH, &mask_value, + REG_INT_STS_A + offset); + mask_value |= (bit_mask); + ret |= twl_i2c_write_u8(TWL_MODULE_PIH, mask_value, + REG_INT_STS_A + offset); /* mask INT_MSK_A/B/C */ + return ret; +} +EXPORT_SYMBOL(twl6030_interrupt_mask); + +int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end) +{ + + int status = 0; + int i; + struct task_struct *task; + int ret; + u8 mask[4]; + + static struct irq_chip twl6030_irq_chip; + mask[1] = 0xFF; + mask[2] = 0xFF; + mask[3] = 0xFF; + ret = twl_i2c_write(TWL_MODULE_PIH, &mask[0], + REG_INT_MSK_LINE_A, 3); /* MASK ALL INT LINES */ + ret = twl_i2c_write(TWL_MODULE_PIH, &mask[0], + REG_INT_MSK_STS_A, 3); /* MASK ALL INT STS */ + ret = twl_i2c_write(TWL_MODULE_PIH, &mask[0], + REG_INT_STS_A, 3); /* clear INT_STS_A,B,C */ + + twl6030_irq_base = irq_base; + + /* install an irq handler for each of the modules; + * clone dummy irq_chip since PIH can't *do* anything + */ + twl6030_irq_chip = dummy_irq_chip; + twl6030_irq_chip.name = "twl6030"; + twl6030_irq_chip.set_type = NULL; + + for (i = irq_base; i < irq_end; i++) { + set_irq_chip_and_handler(i, &twl6030_irq_chip, + handle_simple_irq); + activate_irq(i); + } + + twl6030_irq_next = i; + pr_info("twl6030: %s (irq %d) chaining IRQs %d..%d\n", "PIH", + irq_num, irq_base, twl6030_irq_next - 1); + + /* install an irq handler to demultiplex the TWL6030 interrupt */ + init_completion(&irq_event); + task = kthread_run(twl6030_irq_thread, (void *)irq_num, "twl6030-irq"); + if (IS_ERR(task)) { + pr_err("twl6030: could not create irq %d thread!\n", irq_num); + status = PTR_ERR(task); + goto fail_kthread; + } + + status = request_irq(irq_num, handle_twl6030_pih, IRQF_DISABLED, + "TWL6030-PIH", &irq_event); + if (status < 0) { + pr_err("twl6030: could not claim irq%d: %d\n", irq_num, status); + goto fail_irq; + } + return status; +fail_irq: + free_irq(irq_num, &irq_event); + +fail_kthread: + for (i = irq_base; i < irq_end; i++) + set_irq_chip_and_handler(i, NULL, NULL); + return status; +} + +int twl6030_exit_irq(void) +{ + + if (twl6030_irq_base) { + pr_err("twl6030: can't yet clean up IRQs?\n"); + return -ENOSYS; + } + return 0; +} + diff --git a/drivers/mfd/ucb1400_core.c b/drivers/mfd/ucb1400_core.c new file mode 100644 index 00000000000..85fd9421be9 --- /dev/null +++ b/drivers/mfd/ucb1400_core.c @@ -0,0 +1,157 @@ +/* + * Core functions for: + * Philips UCB1400 multifunction chip + * + * Based on ucb1400_ts.c: + * Author: Nicolas Pitre + * Created: September 25, 2006 + * Copyright: MontaVista Software, Inc. + * + * Spliting done by: Marek Vasut <marek.vasut@gmail.com> + * If something doesnt work and it worked before spliting, e-mail me, + * dont bother Nicolas please ;-) + * + * 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. + * + * This code is heavily based on ucb1x00-*.c copyrighted by Russell King + * covering the UCB1100, UCB1200 and UCB1300.. Support for the UCB1400 has + * been made separate from ucb1x00-core/ucb1x00-ts on Russell's request. + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/ucb1400.h> + +unsigned int ucb1400_adc_read(struct snd_ac97 *ac97, u16 adc_channel, + int adcsync) +{ + unsigned int val; + + if (adcsync) + adc_channel |= UCB_ADC_SYNC_ENA; + + ucb1400_reg_write(ac97, UCB_ADC_CR, UCB_ADC_ENA | adc_channel); + ucb1400_reg_write(ac97, UCB_ADC_CR, UCB_ADC_ENA | adc_channel | + UCB_ADC_START); + + while (!((val = ucb1400_reg_read(ac97, UCB_ADC_DATA)) + & UCB_ADC_DAT_VALID)) + schedule_timeout_uninterruptible(1); + + return val & UCB_ADC_DAT_MASK; +} +EXPORT_SYMBOL_GPL(ucb1400_adc_read); + +static int ucb1400_core_probe(struct device *dev) +{ + int err; + struct ucb1400 *ucb; + struct ucb1400_ts ucb_ts; + struct ucb1400_gpio ucb_gpio; + struct snd_ac97 *ac97; + struct ucb1400_pdata *pdata = dev->platform_data; + + memset(&ucb_ts, 0, sizeof(ucb_ts)); + memset(&ucb_gpio, 0, sizeof(ucb_gpio)); + + ucb = kzalloc(sizeof(struct ucb1400), GFP_KERNEL); + if (!ucb) { + err = -ENOMEM; + goto err; + } + + dev_set_drvdata(dev, ucb); + + ac97 = to_ac97_t(dev); + + ucb_ts.id = ucb1400_reg_read(ac97, UCB_ID); + if (ucb_ts.id != UCB_ID_1400) { + err = -ENODEV; + goto err0; + } + + /* GPIO */ + ucb_gpio.ac97 = ac97; + ucb->ucb1400_gpio = platform_device_alloc("ucb1400_gpio", -1); + if (!ucb->ucb1400_gpio) { + err = -ENOMEM; + goto err0; + } + err = platform_device_add_data(ucb->ucb1400_gpio, &ucb_gpio, + sizeof(ucb_gpio)); + if (err) + goto err1; + err = platform_device_add(ucb->ucb1400_gpio); + if (err) + goto err1; + + /* TOUCHSCREEN */ + ucb_ts.ac97 = ac97; + + if (pdata != NULL && pdata->irq >= 0) + ucb_ts.irq = pdata->irq; + else + ucb_ts.irq = -1; + + ucb->ucb1400_ts = platform_device_alloc("ucb1400_ts", -1); + if (!ucb->ucb1400_ts) { + err = -ENOMEM; + goto err2; + } + err = platform_device_add_data(ucb->ucb1400_ts, &ucb_ts, + sizeof(ucb_ts)); + if (err) + goto err3; + err = platform_device_add(ucb->ucb1400_ts); + if (err) + goto err3; + + return 0; + +err3: + platform_device_put(ucb->ucb1400_ts); +err2: + platform_device_unregister(ucb->ucb1400_gpio); +err1: + platform_device_put(ucb->ucb1400_gpio); +err0: + kfree(ucb); +err: + return err; +} + +static int ucb1400_core_remove(struct device *dev) +{ + struct ucb1400 *ucb = dev_get_drvdata(dev); + + platform_device_unregister(ucb->ucb1400_ts); + platform_device_unregister(ucb->ucb1400_gpio); + + kfree(ucb); + return 0; +} + +static struct device_driver ucb1400_core_driver = { + .name = "ucb1400_core", + .bus = &ac97_bus_type, + .probe = ucb1400_core_probe, + .remove = ucb1400_core_remove, +}; + +static int __init ucb1400_core_init(void) +{ + return driver_register(&ucb1400_core_driver); +} + +static void __exit ucb1400_core_exit(void) +{ + driver_unregister(&ucb1400_core_driver); +} + +module_init(ucb1400_core_init); +module_exit(ucb1400_core_exit); + +MODULE_DESCRIPTION("Philips UCB1400 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/ucb1x00-assabet.c b/drivers/mfd/ucb1x00-assabet.c new file mode 100644 index 00000000000..cea9da60850 --- /dev/null +++ b/drivers/mfd/ucb1x00-assabet.c @@ -0,0 +1,74 @@ +/* + * linux/drivers/mfd/ucb1x00-assabet.c + * + * Copyright (C) 2001-2003 Russell King, All Rights Reserved. + * + * 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. + * + * We handle the machine-specific bits of the UCB1x00 driver here. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/proc_fs.h> +#include <linux/device.h> +#include <linux/mfd/ucb1x00.h> + +#include <mach/dma.h> + + +#define UCB1X00_ATTR(name,input)\ +static ssize_t name##_show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct ucb1x00 *ucb = classdev_to_ucb1x00(dev); \ + int val; \ + ucb1x00_adc_enable(ucb); \ + val = ucb1x00_adc_read(ucb, input, UCB_NOSYNC); \ + ucb1x00_adc_disable(ucb); \ + return sprintf(buf, "%d\n", val); \ +} \ +static DEVICE_ATTR(name,0444,name##_show,NULL) + +UCB1X00_ATTR(vbatt, UCB_ADC_INP_AD1); +UCB1X00_ATTR(vcharger, UCB_ADC_INP_AD0); +UCB1X00_ATTR(batt_temp, UCB_ADC_INP_AD2); + +static int ucb1x00_assabet_add(struct ucb1x00_dev *dev) +{ + device_create_file(&dev->ucb->dev, &dev_attr_vbatt); + device_create_file(&dev->ucb->dev, &dev_attr_vcharger); + device_create_file(&dev->ucb->dev, &dev_attr_batt_temp); + return 0; +} + +static void ucb1x00_assabet_remove(struct ucb1x00_dev *dev) +{ + device_remove_file(&dev->ucb->dev, &dev_attr_batt_temp); + device_remove_file(&dev->ucb->dev, &dev_attr_vcharger); + device_remove_file(&dev->ucb->dev, &dev_attr_vbatt); +} + +static struct ucb1x00_driver ucb1x00_assabet_driver = { + .add = ucb1x00_assabet_add, + .remove = ucb1x00_assabet_remove, +}; + +static int __init ucb1x00_assabet_init(void) +{ + return ucb1x00_register_driver(&ucb1x00_assabet_driver); +} + +static void __exit ucb1x00_assabet_exit(void) +{ + ucb1x00_unregister_driver(&ucb1x00_assabet_driver); +} + +module_init(ucb1x00_assabet_init); +module_exit(ucb1x00_assabet_exit); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("Assabet noddy testing only example ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/ucb1x00-core.c b/drivers/mfd/ucb1x00-core.c new file mode 100644 index 00000000000..252b74188ec --- /dev/null +++ b/drivers/mfd/ucb1x00-core.c @@ -0,0 +1,747 @@ +/* + * linux/drivers/mfd/ucb1x00-core.c + * + * Copyright (C) 2001 Russell King, All Rights Reserved. + * + * 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. + * + * The UCB1x00 core driver provides basic services for handling IO, + * the ADC, interrupts, and accessing registers. It is designed + * such that everything goes through this layer, thereby providing + * a consistent locking methodology, as well as allowing the drivers + * to be used on other non-MCP-enabled hardware platforms. + * + * Note that all locks are private to this file. Nothing else may + * touch them. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/mfd/ucb1x00.h> +#include <linux/gpio.h> + +#include <mach/dma.h> +#include <mach/hardware.h> + +static DEFINE_MUTEX(ucb1x00_mutex); +static LIST_HEAD(ucb1x00_drivers); +static LIST_HEAD(ucb1x00_devices); + +/** + * ucb1x00_io_set_dir - set IO direction + * @ucb: UCB1x00 structure describing chip + * @in: bitfield of IO pins to be set as inputs + * @out: bitfield of IO pins to be set as outputs + * + * Set the IO direction of the ten general purpose IO pins on + * the UCB1x00 chip. The @in bitfield has priority over the + * @out bitfield, in that if you specify a pin as both input + * and output, it will end up as an input. + * + * ucb1x00_enable must have been called to enable the comms + * before using this function. + * + * This function takes a spinlock, disabling interrupts. + */ +void ucb1x00_io_set_dir(struct ucb1x00 *ucb, unsigned int in, unsigned int out) +{ + unsigned long flags; + + spin_lock_irqsave(&ucb->io_lock, flags); + ucb->io_dir |= out; + ucb->io_dir &= ~in; + + ucb1x00_reg_write(ucb, UCB_IO_DIR, ucb->io_dir); + spin_unlock_irqrestore(&ucb->io_lock, flags); +} + +/** + * ucb1x00_io_write - set or clear IO outputs + * @ucb: UCB1x00 structure describing chip + * @set: bitfield of IO pins to set to logic '1' + * @clear: bitfield of IO pins to set to logic '0' + * + * Set the IO output state of the specified IO pins. The value + * is retained if the pins are subsequently configured as inputs. + * The @clear bitfield has priority over the @set bitfield - + * outputs will be cleared. + * + * ucb1x00_enable must have been called to enable the comms + * before using this function. + * + * This function takes a spinlock, disabling interrupts. + */ +void ucb1x00_io_write(struct ucb1x00 *ucb, unsigned int set, unsigned int clear) +{ + unsigned long flags; + + spin_lock_irqsave(&ucb->io_lock, flags); + ucb->io_out |= set; + ucb->io_out &= ~clear; + + ucb1x00_reg_write(ucb, UCB_IO_DATA, ucb->io_out); + spin_unlock_irqrestore(&ucb->io_lock, flags); +} + +/** + * ucb1x00_io_read - read the current state of the IO pins + * @ucb: UCB1x00 structure describing chip + * + * Return a bitfield describing the logic state of the ten + * general purpose IO pins. + * + * ucb1x00_enable must have been called to enable the comms + * before using this function. + * + * This function does not take any semaphores or spinlocks. + */ +unsigned int ucb1x00_io_read(struct ucb1x00 *ucb) +{ + return ucb1x00_reg_read(ucb, UCB_IO_DATA); +} + +static void ucb1x00_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct ucb1x00 *ucb = container_of(chip, struct ucb1x00, gpio); + unsigned long flags; + + spin_lock_irqsave(&ucb->io_lock, flags); + if (value) + ucb->io_out |= 1 << offset; + else + ucb->io_out &= ~(1 << offset); + + ucb1x00_reg_write(ucb, UCB_IO_DATA, ucb->io_out); + spin_unlock_irqrestore(&ucb->io_lock, flags); +} + +static int ucb1x00_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct ucb1x00 *ucb = container_of(chip, struct ucb1x00, gpio); + return ucb1x00_reg_read(ucb, UCB_IO_DATA) & (1 << offset); +} + +static int ucb1x00_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct ucb1x00 *ucb = container_of(chip, struct ucb1x00, gpio); + unsigned long flags; + + spin_lock_irqsave(&ucb->io_lock, flags); + ucb->io_dir &= ~(1 << offset); + ucb1x00_reg_write(ucb, UCB_IO_DIR, ucb->io_dir); + spin_unlock_irqrestore(&ucb->io_lock, flags); + + return 0; +} + +static int ucb1x00_gpio_direction_output(struct gpio_chip *chip, unsigned offset + , int value) +{ + struct ucb1x00 *ucb = container_of(chip, struct ucb1x00, gpio); + unsigned long flags; + + spin_lock_irqsave(&ucb->io_lock, flags); + ucb->io_dir |= (1 << offset); + ucb1x00_reg_write(ucb, UCB_IO_DIR, ucb->io_dir); + + if (value) + ucb->io_out |= 1 << offset; + else + ucb->io_out &= ~(1 << offset); + ucb1x00_reg_write(ucb, UCB_IO_DATA, ucb->io_out); + spin_unlock_irqrestore(&ucb->io_lock, flags); + + return 0; +} + +/* + * UCB1300 data sheet says we must: + * 1. enable ADC => 5us (including reference startup time) + * 2. select input => 51*tsibclk => 4.3us + * 3. start conversion => 102*tsibclk => 8.5us + * (tsibclk = 1/11981000) + * Period between SIB 128-bit frames = 10.7us + */ + +/** + * ucb1x00_adc_enable - enable the ADC converter + * @ucb: UCB1x00 structure describing chip + * + * Enable the ucb1x00 and ADC converter on the UCB1x00 for use. + * Any code wishing to use the ADC converter must call this + * function prior to using it. + * + * This function takes the ADC semaphore to prevent two or more + * concurrent uses, and therefore may sleep. As a result, it + * can only be called from process context, not interrupt + * context. + * + * You should release the ADC as soon as possible using + * ucb1x00_adc_disable. + */ +void ucb1x00_adc_enable(struct ucb1x00 *ucb) +{ + down(&ucb->adc_sem); + + ucb->adc_cr |= UCB_ADC_ENA; + + ucb1x00_enable(ucb); + ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr); +} + +/** + * ucb1x00_adc_read - read the specified ADC channel + * @ucb: UCB1x00 structure describing chip + * @adc_channel: ADC channel mask + * @sync: wait for syncronisation pulse. + * + * Start an ADC conversion and wait for the result. Note that + * synchronised ADC conversions (via the ADCSYNC pin) must wait + * until the trigger is asserted and the conversion is finished. + * + * This function currently spins waiting for the conversion to + * complete (2 frames max without sync). + * + * If called for a synchronised ADC conversion, it may sleep + * with the ADC semaphore held. + */ +unsigned int ucb1x00_adc_read(struct ucb1x00 *ucb, int adc_channel, int sync) +{ + unsigned int val; + + if (sync) + adc_channel |= UCB_ADC_SYNC_ENA; + + ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr | adc_channel); + ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr | adc_channel | UCB_ADC_START); + + for (;;) { + val = ucb1x00_reg_read(ucb, UCB_ADC_DATA); + if (val & UCB_ADC_DAT_VAL) + break; + /* yield to other processes */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + } + + return UCB_ADC_DAT(val); +} + +/** + * ucb1x00_adc_disable - disable the ADC converter + * @ucb: UCB1x00 structure describing chip + * + * Disable the ADC converter and release the ADC semaphore. + */ +void ucb1x00_adc_disable(struct ucb1x00 *ucb) +{ + ucb->adc_cr &= ~UCB_ADC_ENA; + ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr); + ucb1x00_disable(ucb); + + up(&ucb->adc_sem); +} + +/* + * UCB1x00 Interrupt handling. + * + * The UCB1x00 can generate interrupts when the SIBCLK is stopped. + * Since we need to read an internal register, we must re-enable + * SIBCLK to talk to the chip. We leave the clock running until + * we have finished processing all interrupts from the chip. + */ +static irqreturn_t ucb1x00_irq(int irqnr, void *devid) +{ + struct ucb1x00 *ucb = devid; + struct ucb1x00_irq *irq; + unsigned int isr, i; + + ucb1x00_enable(ucb); + isr = ucb1x00_reg_read(ucb, UCB_IE_STATUS); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, isr); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0); + + for (i = 0, irq = ucb->irq_handler; i < 16 && isr; i++, isr >>= 1, irq++) + if (isr & 1 && irq->fn) + irq->fn(i, irq->devid); + ucb1x00_disable(ucb); + + return IRQ_HANDLED; +} + +/** + * ucb1x00_hook_irq - hook a UCB1x00 interrupt + * @ucb: UCB1x00 structure describing chip + * @idx: interrupt index + * @fn: function to call when interrupt is triggered + * @devid: device id to pass to interrupt handler + * + * Hook the specified interrupt. You can only register one handler + * for each interrupt source. The interrupt source is not enabled + * by this function; use ucb1x00_enable_irq instead. + * + * Interrupt handlers will be called with other interrupts enabled. + * + * Returns zero on success, or one of the following errors: + * -EINVAL if the interrupt index is invalid + * -EBUSY if the interrupt has already been hooked + */ +int ucb1x00_hook_irq(struct ucb1x00 *ucb, unsigned int idx, void (*fn)(int, void *), void *devid) +{ + struct ucb1x00_irq *irq; + int ret = -EINVAL; + + if (idx < 16) { + irq = ucb->irq_handler + idx; + ret = -EBUSY; + + spin_lock_irq(&ucb->lock); + if (irq->fn == NULL) { + irq->devid = devid; + irq->fn = fn; + ret = 0; + } + spin_unlock_irq(&ucb->lock); + } + return ret; +} + +/** + * ucb1x00_enable_irq - enable an UCB1x00 interrupt source + * @ucb: UCB1x00 structure describing chip + * @idx: interrupt index + * @edges: interrupt edges to enable + * + * Enable the specified interrupt to trigger on %UCB_RISING, + * %UCB_FALLING or both edges. The interrupt should have been + * hooked by ucb1x00_hook_irq. + */ +void ucb1x00_enable_irq(struct ucb1x00 *ucb, unsigned int idx, int edges) +{ + unsigned long flags; + + if (idx < 16) { + spin_lock_irqsave(&ucb->lock, flags); + + ucb1x00_enable(ucb); + if (edges & UCB_RISING) { + ucb->irq_ris_enbl |= 1 << idx; + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl); + } + if (edges & UCB_FALLING) { + ucb->irq_fal_enbl |= 1 << idx; + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl); + } + ucb1x00_disable(ucb); + spin_unlock_irqrestore(&ucb->lock, flags); + } +} + +/** + * ucb1x00_disable_irq - disable an UCB1x00 interrupt source + * @ucb: UCB1x00 structure describing chip + * @edges: interrupt edges to disable + * + * Disable the specified interrupt triggering on the specified + * (%UCB_RISING, %UCB_FALLING or both) edges. + */ +void ucb1x00_disable_irq(struct ucb1x00 *ucb, unsigned int idx, int edges) +{ + unsigned long flags; + + if (idx < 16) { + spin_lock_irqsave(&ucb->lock, flags); + + ucb1x00_enable(ucb); + if (edges & UCB_RISING) { + ucb->irq_ris_enbl &= ~(1 << idx); + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl); + } + if (edges & UCB_FALLING) { + ucb->irq_fal_enbl &= ~(1 << idx); + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl); + } + ucb1x00_disable(ucb); + spin_unlock_irqrestore(&ucb->lock, flags); + } +} + +/** + * ucb1x00_free_irq - disable and free the specified UCB1x00 interrupt + * @ucb: UCB1x00 structure describing chip + * @idx: interrupt index + * @devid: device id. + * + * Disable the interrupt source and remove the handler. devid must + * match the devid passed when hooking the interrupt. + * + * Returns zero on success, or one of the following errors: + * -EINVAL if the interrupt index is invalid + * -ENOENT if devid does not match + */ +int ucb1x00_free_irq(struct ucb1x00 *ucb, unsigned int idx, void *devid) +{ + struct ucb1x00_irq *irq; + int ret; + + if (idx >= 16) + goto bad; + + irq = ucb->irq_handler + idx; + ret = -ENOENT; + + spin_lock_irq(&ucb->lock); + if (irq->devid == devid) { + ucb->irq_ris_enbl &= ~(1 << idx); + ucb->irq_fal_enbl &= ~(1 << idx); + + ucb1x00_enable(ucb); + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl); + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl); + ucb1x00_disable(ucb); + + irq->fn = NULL; + irq->devid = NULL; + ret = 0; + } + spin_unlock_irq(&ucb->lock); + return ret; + +bad: + printk(KERN_ERR "Freeing bad UCB1x00 irq %d\n", idx); + return -EINVAL; +} + +static int ucb1x00_add_dev(struct ucb1x00 *ucb, struct ucb1x00_driver *drv) +{ + struct ucb1x00_dev *dev; + int ret = -ENOMEM; + + dev = kmalloc(sizeof(struct ucb1x00_dev), GFP_KERNEL); + if (dev) { + dev->ucb = ucb; + dev->drv = drv; + + ret = drv->add(dev); + + if (ret == 0) { + list_add(&dev->dev_node, &ucb->devs); + list_add(&dev->drv_node, &drv->devs); + } else { + kfree(dev); + } + } + return ret; +} + +static void ucb1x00_remove_dev(struct ucb1x00_dev *dev) +{ + dev->drv->remove(dev); + list_del(&dev->dev_node); + list_del(&dev->drv_node); + kfree(dev); +} + +/* + * Try to probe our interrupt, rather than relying on lots of + * hard-coded machine dependencies. For reference, the expected + * IRQ mappings are: + * + * Machine Default IRQ + * adsbitsy IRQ_GPCIN4 + * cerf IRQ_GPIO_UCB1200_IRQ + * flexanet IRQ_GPIO_GUI + * freebird IRQ_GPIO_FREEBIRD_UCB1300_IRQ + * graphicsclient ADS_EXT_IRQ(8) + * graphicsmaster ADS_EXT_IRQ(8) + * lart LART_IRQ_UCB1200 + * omnimeter IRQ_GPIO23 + * pfs168 IRQ_GPIO_UCB1300_IRQ + * simpad IRQ_GPIO_UCB1300_IRQ + * shannon SHANNON_IRQ_GPIO_IRQ_CODEC + * yopy IRQ_GPIO_UCB1200_IRQ + */ +static int ucb1x00_detect_irq(struct ucb1x00 *ucb) +{ + unsigned long mask; + + mask = probe_irq_on(); + if (!mask) { + probe_irq_off(mask); + return NO_IRQ; + } + + /* + * Enable the ADC interrupt. + */ + ucb1x00_reg_write(ucb, UCB_IE_RIS, UCB_IE_ADC); + ucb1x00_reg_write(ucb, UCB_IE_FAL, UCB_IE_ADC); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0xffff); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0); + + /* + * Cause an ADC interrupt. + */ + ucb1x00_reg_write(ucb, UCB_ADC_CR, UCB_ADC_ENA); + ucb1x00_reg_write(ucb, UCB_ADC_CR, UCB_ADC_ENA | UCB_ADC_START); + + /* + * Wait for the conversion to complete. + */ + while ((ucb1x00_reg_read(ucb, UCB_ADC_DATA) & UCB_ADC_DAT_VAL) == 0); + ucb1x00_reg_write(ucb, UCB_ADC_CR, 0); + + /* + * Disable and clear interrupt. + */ + ucb1x00_reg_write(ucb, UCB_IE_RIS, 0); + ucb1x00_reg_write(ucb, UCB_IE_FAL, 0); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0xffff); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0); + + /* + * Read triggered interrupt. + */ + return probe_irq_off(mask); +} + +static void ucb1x00_release(struct device *dev) +{ + struct ucb1x00 *ucb = classdev_to_ucb1x00(dev); + kfree(ucb); +} + +static struct class ucb1x00_class = { + .name = "ucb1x00", + .dev_release = ucb1x00_release, +}; + +static int ucb1x00_probe(struct mcp *mcp) +{ + struct ucb1x00 *ucb; + struct ucb1x00_driver *drv; + unsigned int id; + int ret = -ENODEV; + int temp; + + mcp_enable(mcp); + id = mcp_reg_read(mcp, UCB_ID); + + if (id != UCB_ID_1200 && id != UCB_ID_1300 && id != UCB_ID_TC35143) { + printk(KERN_WARNING "UCB1x00 ID not found: %04x\n", id); + goto err_disable; + } + + ucb = kzalloc(sizeof(struct ucb1x00), GFP_KERNEL); + ret = -ENOMEM; + if (!ucb) + goto err_disable; + + + ucb->dev.class = &ucb1x00_class; + ucb->dev.parent = &mcp->attached_device; + dev_set_name(&ucb->dev, "ucb1x00"); + + spin_lock_init(&ucb->lock); + spin_lock_init(&ucb->io_lock); + sema_init(&ucb->adc_sem, 1); + + ucb->id = id; + ucb->mcp = mcp; + ucb->irq = ucb1x00_detect_irq(ucb); + if (ucb->irq == NO_IRQ) { + printk(KERN_ERR "UCB1x00: IRQ probe failed\n"); + ret = -ENODEV; + goto err_free; + } + + ucb->gpio.base = -1; + if (mcp->gpio_base != 0) { + ucb->gpio.label = dev_name(&ucb->dev); + ucb->gpio.base = mcp->gpio_base; + ucb->gpio.ngpio = 10; + ucb->gpio.set = ucb1x00_gpio_set; + ucb->gpio.get = ucb1x00_gpio_get; + ucb->gpio.direction_input = ucb1x00_gpio_direction_input; + ucb->gpio.direction_output = ucb1x00_gpio_direction_output; + ret = gpiochip_add(&ucb->gpio); + if (ret) + goto err_free; + } else + dev_info(&ucb->dev, "gpio_base not set so no gpiolib support"); + + ret = request_irq(ucb->irq, ucb1x00_irq, IRQF_TRIGGER_RISING, + "UCB1x00", ucb); + if (ret) { + printk(KERN_ERR "ucb1x00: unable to grab irq%d: %d\n", + ucb->irq, ret); + goto err_gpio; + } + + mcp_set_drvdata(mcp, ucb); + + ret = device_register(&ucb->dev); + if (ret) + goto err_irq; + + + INIT_LIST_HEAD(&ucb->devs); + mutex_lock(&ucb1x00_mutex); + list_add(&ucb->node, &ucb1x00_devices); + list_for_each_entry(drv, &ucb1x00_drivers, node) { + ucb1x00_add_dev(ucb, drv); + } + mutex_unlock(&ucb1x00_mutex); + + goto out; + + err_irq: + free_irq(ucb->irq, ucb); + err_gpio: + if (ucb->gpio.base != -1) + temp = gpiochip_remove(&ucb->gpio); + err_free: + kfree(ucb); + err_disable: + mcp_disable(mcp); + out: + return ret; +} + +static void ucb1x00_remove(struct mcp *mcp) +{ + struct ucb1x00 *ucb = mcp_get_drvdata(mcp); + struct list_head *l, *n; + int ret; + + mutex_lock(&ucb1x00_mutex); + list_del(&ucb->node); + list_for_each_safe(l, n, &ucb->devs) { + struct ucb1x00_dev *dev = list_entry(l, struct ucb1x00_dev, dev_node); + ucb1x00_remove_dev(dev); + } + mutex_unlock(&ucb1x00_mutex); + + if (ucb->gpio.base != -1) { + ret = gpiochip_remove(&ucb->gpio); + if (ret) + dev_err(&ucb->dev, "Can't remove gpio chip: %d\n", ret); + } + + free_irq(ucb->irq, ucb); + device_unregister(&ucb->dev); +} + +int ucb1x00_register_driver(struct ucb1x00_driver *drv) +{ + struct ucb1x00 *ucb; + + INIT_LIST_HEAD(&drv->devs); + mutex_lock(&ucb1x00_mutex); + list_add(&drv->node, &ucb1x00_drivers); + list_for_each_entry(ucb, &ucb1x00_devices, node) { + ucb1x00_add_dev(ucb, drv); + } + mutex_unlock(&ucb1x00_mutex); + return 0; +} + +void ucb1x00_unregister_driver(struct ucb1x00_driver *drv) +{ + struct list_head *n, *l; + + mutex_lock(&ucb1x00_mutex); + list_del(&drv->node); + list_for_each_safe(l, n, &drv->devs) { + struct ucb1x00_dev *dev = list_entry(l, struct ucb1x00_dev, drv_node); + ucb1x00_remove_dev(dev); + } + mutex_unlock(&ucb1x00_mutex); +} + +static int ucb1x00_suspend(struct mcp *mcp, pm_message_t state) +{ + struct ucb1x00 *ucb = mcp_get_drvdata(mcp); + struct ucb1x00_dev *dev; + + mutex_lock(&ucb1x00_mutex); + list_for_each_entry(dev, &ucb->devs, dev_node) { + if (dev->drv->suspend) + dev->drv->suspend(dev, state); + } + mutex_unlock(&ucb1x00_mutex); + return 0; +} + +static int ucb1x00_resume(struct mcp *mcp) +{ + struct ucb1x00 *ucb = mcp_get_drvdata(mcp); + struct ucb1x00_dev *dev; + + ucb1x00_reg_write(ucb, UCB_IO_DIR, ucb->io_dir); + mutex_lock(&ucb1x00_mutex); + list_for_each_entry(dev, &ucb->devs, dev_node) { + if (dev->drv->resume) + dev->drv->resume(dev); + } + mutex_unlock(&ucb1x00_mutex); + return 0; +} + +static struct mcp_driver ucb1x00_driver = { + .drv = { + .name = "ucb1x00", + }, + .probe = ucb1x00_probe, + .remove = ucb1x00_remove, + .suspend = ucb1x00_suspend, + .resume = ucb1x00_resume, +}; + +static int __init ucb1x00_init(void) +{ + int ret = class_register(&ucb1x00_class); + if (ret == 0) { + ret = mcp_driver_register(&ucb1x00_driver); + if (ret) + class_unregister(&ucb1x00_class); + } + return ret; +} + +static void __exit ucb1x00_exit(void) +{ + mcp_driver_unregister(&ucb1x00_driver); + class_unregister(&ucb1x00_class); +} + +module_init(ucb1x00_init); +module_exit(ucb1x00_exit); + +EXPORT_SYMBOL(ucb1x00_io_set_dir); +EXPORT_SYMBOL(ucb1x00_io_write); +EXPORT_SYMBOL(ucb1x00_io_read); + +EXPORT_SYMBOL(ucb1x00_adc_enable); +EXPORT_SYMBOL(ucb1x00_adc_read); +EXPORT_SYMBOL(ucb1x00_adc_disable); + +EXPORT_SYMBOL(ucb1x00_hook_irq); +EXPORT_SYMBOL(ucb1x00_free_irq); +EXPORT_SYMBOL(ucb1x00_enable_irq); +EXPORT_SYMBOL(ucb1x00_disable_irq); + +EXPORT_SYMBOL(ucb1x00_register_driver); +EXPORT_SYMBOL(ucb1x00_unregister_driver); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("UCB1x00 core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/ucb1x00-ts.c b/drivers/mfd/ucb1x00-ts.c new file mode 100644 index 00000000000..000cb414a78 --- /dev/null +++ b/drivers/mfd/ucb1x00-ts.c @@ -0,0 +1,438 @@ +/* + * Touchscreen driver for UCB1x00-based touchscreens + * + * Copyright (C) 2001 Russell King, All Rights Reserved. + * Copyright (C) 2005 Pavel Machek + * + * 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. + * + * 21-Jan-2002 <jco@ict.es> : + * + * Added support for synchronous A/D mode. This mode is useful to + * avoid noise induced in the touchpanel by the LCD, provided that + * the UCB1x00 has a valid LCD sync signal routed to its ADCSYNC pin. + * It is important to note that the signal connected to the ADCSYNC + * pin should provide pulses even when the LCD is blanked, otherwise + * a pen touch needed to unblank the LCD will never be read. + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/smp.h> +#include <linux/sched.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/input.h> +#include <linux/device.h> +#include <linux/freezer.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/mfd/ucb1x00.h> + +#include <mach/dma.h> +#include <mach/collie.h> +#include <asm/mach-types.h> + + + +struct ucb1x00_ts { + struct input_dev *idev; + struct ucb1x00 *ucb; + + wait_queue_head_t irq_wait; + struct task_struct *rtask; + u16 x_res; + u16 y_res; + + unsigned int restart:1; + unsigned int adcsync:1; +}; + +static int adcsync; + +static inline void ucb1x00_ts_evt_add(struct ucb1x00_ts *ts, u16 pressure, u16 x, u16 y) +{ + struct input_dev *idev = ts->idev; + + input_report_abs(idev, ABS_X, x); + input_report_abs(idev, ABS_Y, y); + input_report_abs(idev, ABS_PRESSURE, pressure); + input_sync(idev); +} + +static inline void ucb1x00_ts_event_release(struct ucb1x00_ts *ts) +{ + struct input_dev *idev = ts->idev; + + input_report_abs(idev, ABS_PRESSURE, 0); + input_sync(idev); +} + +/* + * Switch to interrupt mode. + */ +static inline void ucb1x00_ts_mode_int(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_INT); +} + +/* + * Switch to pressure mode, and read pressure. We don't need to wait + * here, since both plates are being driven. + */ +static inline unsigned int ucb1x00_ts_read_pressure(struct ucb1x00_ts *ts) +{ + if (machine_is_collie()) { + ucb1x00_io_write(ts->ucb, COLLIE_TC35143_GPIO_TBL_CHK, 0); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSPX_POW | UCB_TS_CR_TSMX_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(55); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_AD2, ts->adcsync); + } else { + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync); + } +} + +/* + * Switch to X position mode and measure Y plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ucb1x00_ts_read_xpos(struct ucb1x00_ts *ts) +{ + if (machine_is_collie()) + ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); + else { + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + } + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(55); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync); +} + +/* + * Switch to Y position mode and measure X plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ucb1x00_ts_read_ypos(struct ucb1x00_ts *ts) +{ + if (machine_is_collie()) + ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); + else { + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + } + + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(55); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPX, ts->adcsync); +} + +/* + * Switch to X plate resistance mode. Set MX to ground, PX to + * supply. Measure current. + */ +static inline unsigned int ucb1x00_ts_read_xres(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync); +} + +/* + * Switch to Y plate resistance mode. Set MY to ground, PY to + * supply. Measure current. + */ +static inline unsigned int ucb1x00_ts_read_yres(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync); +} + +static inline int ucb1x00_ts_pen_down(struct ucb1x00_ts *ts) +{ + unsigned int val = ucb1x00_reg_read(ts->ucb, UCB_TS_CR); + + if (machine_is_collie()) + return (!(val & (UCB_TS_CR_TSPX_LOW))); + else + return (val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW)); +} + +/* + * This is a RT kernel thread that handles the ADC accesses + * (mainly so we can use semaphores in the UCB1200 core code + * to serialise accesses to the ADC). + */ +static int ucb1x00_thread(void *_ts) +{ + struct ucb1x00_ts *ts = _ts; + DECLARE_WAITQUEUE(wait, current); + int valid = 0; + + set_freezable(); + add_wait_queue(&ts->irq_wait, &wait); + while (!kthread_should_stop()) { + unsigned int x, y, p; + signed long timeout; + + ts->restart = 0; + + ucb1x00_adc_enable(ts->ucb); + + x = ucb1x00_ts_read_xpos(ts); + y = ucb1x00_ts_read_ypos(ts); + p = ucb1x00_ts_read_pressure(ts); + + /* + * Switch back to interrupt mode. + */ + ucb1x00_ts_mode_int(ts); + ucb1x00_adc_disable(ts->ucb); + + msleep(10); + + ucb1x00_enable(ts->ucb); + + + if (ucb1x00_ts_pen_down(ts)) { + set_current_state(TASK_INTERRUPTIBLE); + + ucb1x00_enable_irq(ts->ucb, UCB_IRQ_TSPX, machine_is_collie() ? UCB_RISING : UCB_FALLING); + ucb1x00_disable(ts->ucb); + + /* + * If we spat out a valid sample set last time, + * spit out a "pen off" sample here. + */ + if (valid) { + ucb1x00_ts_event_release(ts); + valid = 0; + } + + timeout = MAX_SCHEDULE_TIMEOUT; + } else { + ucb1x00_disable(ts->ucb); + + /* + * Filtering is policy. Policy belongs in user + * space. We therefore leave it to user space + * to do any filtering they please. + */ + if (!ts->restart) { + ucb1x00_ts_evt_add(ts, p, x, y); + valid = 1; + } + + set_current_state(TASK_INTERRUPTIBLE); + timeout = HZ / 100; + } + + try_to_freeze(); + + schedule_timeout(timeout); + } + + remove_wait_queue(&ts->irq_wait, &wait); + + ts->rtask = NULL; + return 0; +} + +/* + * We only detect touch screen _touches_ with this interrupt + * handler, and even then we just schedule our task. + */ +static void ucb1x00_ts_irq(int idx, void *id) +{ + struct ucb1x00_ts *ts = id; + + ucb1x00_disable_irq(ts->ucb, UCB_IRQ_TSPX, UCB_FALLING); + wake_up(&ts->irq_wait); +} + +static int ucb1x00_ts_open(struct input_dev *idev) +{ + struct ucb1x00_ts *ts = input_get_drvdata(idev); + int ret = 0; + + BUG_ON(ts->rtask); + + init_waitqueue_head(&ts->irq_wait); + ret = ucb1x00_hook_irq(ts->ucb, UCB_IRQ_TSPX, ucb1x00_ts_irq, ts); + if (ret < 0) + goto out; + + /* + * If we do this at all, we should allow the user to + * measure and read the X and Y resistance at any time. + */ + ucb1x00_adc_enable(ts->ucb); + ts->x_res = ucb1x00_ts_read_xres(ts); + ts->y_res = ucb1x00_ts_read_yres(ts); + ucb1x00_adc_disable(ts->ucb); + + ts->rtask = kthread_run(ucb1x00_thread, ts, "ktsd"); + if (!IS_ERR(ts->rtask)) { + ret = 0; + } else { + ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts); + ts->rtask = NULL; + ret = -EFAULT; + } + + out: + return ret; +} + +/* + * Release touchscreen resources. Disable IRQs. + */ +static void ucb1x00_ts_close(struct input_dev *idev) +{ + struct ucb1x00_ts *ts = input_get_drvdata(idev); + + if (ts->rtask) + kthread_stop(ts->rtask); + + ucb1x00_enable(ts->ucb); + ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, 0); + ucb1x00_disable(ts->ucb); +} + +#ifdef CONFIG_PM +static int ucb1x00_ts_resume(struct ucb1x00_dev *dev) +{ + struct ucb1x00_ts *ts = dev->priv; + + if (ts->rtask != NULL) { + /* + * Restart the TS thread to ensure the + * TS interrupt mode is set up again + * after sleep. + */ + ts->restart = 1; + wake_up(&ts->irq_wait); + } + return 0; +} +#else +#define ucb1x00_ts_resume NULL +#endif + + +/* + * Initialisation. + */ +static int ucb1x00_ts_add(struct ucb1x00_dev *dev) +{ + struct ucb1x00_ts *ts; + struct input_dev *idev; + int err; + + ts = kzalloc(sizeof(struct ucb1x00_ts), GFP_KERNEL); + idev = input_allocate_device(); + if (!ts || !idev) { + err = -ENOMEM; + goto fail; + } + + ts->ucb = dev->ucb; + ts->idev = idev; + ts->adcsync = adcsync ? UCB_SYNC : UCB_NOSYNC; + + idev->name = "Touchscreen panel"; + idev->id.product = ts->ucb->id; + idev->open = ucb1x00_ts_open; + idev->close = ucb1x00_ts_close; + + __set_bit(EV_ABS, idev->evbit); + __set_bit(ABS_X, idev->absbit); + __set_bit(ABS_Y, idev->absbit); + __set_bit(ABS_PRESSURE, idev->absbit); + + input_set_drvdata(idev, ts); + + err = input_register_device(idev); + if (err) + goto fail; + + dev->priv = ts; + + return 0; + + fail: + input_free_device(idev); + kfree(ts); + return err; +} + +static void ucb1x00_ts_remove(struct ucb1x00_dev *dev) +{ + struct ucb1x00_ts *ts = dev->priv; + + input_unregister_device(ts->idev); + kfree(ts); +} + +static struct ucb1x00_driver ucb1x00_ts_driver = { + .add = ucb1x00_ts_add, + .remove = ucb1x00_ts_remove, + .resume = ucb1x00_ts_resume, +}; + +static int __init ucb1x00_ts_init(void) +{ + return ucb1x00_register_driver(&ucb1x00_ts_driver); +} + +static void __exit ucb1x00_ts_exit(void) +{ + ucb1x00_unregister_driver(&ucb1x00_ts_driver); +} + +module_param(adcsync, int, 0444); +module_init(ucb1x00_ts_init); +module_exit(ucb1x00_ts_exit); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("UCB1x00 touchscreen driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c new file mode 100644 index 00000000000..4b2021af1d9 --- /dev/null +++ b/drivers/mfd/wm831x-core.c @@ -0,0 +1,1699 @@ +/* + * wm831x-core.c -- Device access for Wolfson WM831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/bcd.h> +#include <linux/delay.h> +#include <linux/mfd/core.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/pdata.h> +#include <linux/mfd/wm831x/irq.h> +#include <linux/mfd/wm831x/auxadc.h> +#include <linux/mfd/wm831x/otp.h> +#include <linux/mfd/wm831x/regulator.h> + +/* Current settings - values are 2*2^(reg_val/4) microamps. These are + * exported since they are used by multiple drivers. + */ +int wm831x_isinkv_values[WM831X_ISINK_MAX_ISEL + 1] = { + 2, + 2, + 3, + 3, + 4, + 5, + 6, + 7, + 8, + 10, + 11, + 13, + 16, + 19, + 23, + 27, + 32, + 38, + 45, + 54, + 64, + 76, + 91, + 108, + 128, + 152, + 181, + 215, + 256, + 304, + 362, + 431, + 512, + 609, + 724, + 861, + 1024, + 1218, + 1448, + 1722, + 2048, + 2435, + 2896, + 3444, + 4096, + 4871, + 5793, + 6889, + 8192, + 9742, + 11585, + 13777, + 16384, + 19484, + 23170, + 27554, +}; +EXPORT_SYMBOL_GPL(wm831x_isinkv_values); + +enum wm831x_parent { + WM8310 = 0x8310, + WM8311 = 0x8311, + WM8312 = 0x8312, + WM8320 = 0x8320, +}; + +static int wm831x_reg_locked(struct wm831x *wm831x, unsigned short reg) +{ + if (!wm831x->locked) + return 0; + + switch (reg) { + case WM831X_WATCHDOG: + case WM831X_DC4_CONTROL: + case WM831X_ON_PIN_CONTROL: + case WM831X_BACKUP_CHARGER_CONTROL: + case WM831X_CHARGER_CONTROL_1: + case WM831X_CHARGER_CONTROL_2: + return 1; + + default: + return 0; + } +} + +/** + * wm831x_reg_unlock: Unlock user keyed registers + * + * The WM831x has a user key preventing writes to particularly + * critical registers. This function locks those registers, + * allowing writes to them. + */ +void wm831x_reg_lock(struct wm831x *wm831x) +{ + int ret; + + ret = wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0); + if (ret == 0) { + dev_vdbg(wm831x->dev, "Registers locked\n"); + + mutex_lock(&wm831x->io_lock); + WARN_ON(wm831x->locked); + wm831x->locked = 1; + mutex_unlock(&wm831x->io_lock); + } else { + dev_err(wm831x->dev, "Failed to lock registers: %d\n", ret); + } + +} +EXPORT_SYMBOL_GPL(wm831x_reg_lock); + +/** + * wm831x_reg_unlock: Unlock user keyed registers + * + * The WM831x has a user key preventing writes to particularly + * critical registers. This function locks those registers, + * preventing spurious writes. + */ +int wm831x_reg_unlock(struct wm831x *wm831x) +{ + int ret; + + /* 0x9716 is the value required to unlock the registers */ + ret = wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0x9716); + if (ret == 0) { + dev_vdbg(wm831x->dev, "Registers unlocked\n"); + + mutex_lock(&wm831x->io_lock); + WARN_ON(!wm831x->locked); + wm831x->locked = 0; + mutex_unlock(&wm831x->io_lock); + } + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_reg_unlock); + +static int wm831x_read(struct wm831x *wm831x, unsigned short reg, + int bytes, void *dest) +{ + int ret, i; + u16 *buf = dest; + + BUG_ON(bytes % 2); + BUG_ON(bytes <= 0); + + ret = wm831x->read_dev(wm831x, reg, bytes, dest); + if (ret < 0) + return ret; + + for (i = 0; i < bytes / 2; i++) { + buf[i] = be16_to_cpu(buf[i]); + + dev_vdbg(wm831x->dev, "Read %04x from R%d(0x%x)\n", + buf[i], reg + i, reg + i); + } + + return 0; +} + +/** + * wm831x_reg_read: Read a single WM831x register. + * + * @wm831x: Device to read from. + * @reg: Register to read. + */ +int wm831x_reg_read(struct wm831x *wm831x, unsigned short reg) +{ + unsigned short val; + int ret; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_read(wm831x, reg, 2, &val); + + mutex_unlock(&wm831x->io_lock); + + if (ret < 0) + return ret; + else + return val; +} +EXPORT_SYMBOL_GPL(wm831x_reg_read); + +/** + * wm831x_bulk_read: Read multiple WM831x registers + * + * @wm831x: Device to read from + * @reg: First register + * @count: Number of registers + * @buf: Buffer to fill. + */ +int wm831x_bulk_read(struct wm831x *wm831x, unsigned short reg, + int count, u16 *buf) +{ + int ret; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_read(wm831x, reg, count * 2, buf); + + mutex_unlock(&wm831x->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_bulk_read); + +static int wm831x_write(struct wm831x *wm831x, unsigned short reg, + int bytes, void *src) +{ + u16 *buf = src; + int i; + + BUG_ON(bytes % 2); + BUG_ON(bytes <= 0); + + for (i = 0; i < bytes / 2; i++) { + if (wm831x_reg_locked(wm831x, reg)) + return -EPERM; + + dev_vdbg(wm831x->dev, "Write %04x to R%d(0x%x)\n", + buf[i], reg + i, reg + i); + + buf[i] = cpu_to_be16(buf[i]); + } + + return wm831x->write_dev(wm831x, reg, bytes, src); +} + +/** + * wm831x_reg_write: Write a single WM831x register. + * + * @wm831x: Device to write to. + * @reg: Register to write to. + * @val: Value to write. + */ +int wm831x_reg_write(struct wm831x *wm831x, unsigned short reg, + unsigned short val) +{ + int ret; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_write(wm831x, reg, 2, &val); + + mutex_unlock(&wm831x->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_reg_write); + +/** + * wm831x_set_bits: Set the value of a bitfield in a WM831x register + * + * @wm831x: Device to write to. + * @reg: Register to write to. + * @mask: Mask of bits to set. + * @val: Value to set (unshifted) + */ +int wm831x_set_bits(struct wm831x *wm831x, unsigned short reg, + unsigned short mask, unsigned short val) +{ + int ret; + u16 r; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_read(wm831x, reg, 2, &r); + if (ret < 0) + goto out; + + r &= ~mask; + r |= val; + + ret = wm831x_write(wm831x, reg, 2, &r); + +out: + mutex_unlock(&wm831x->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_set_bits); + +/** + * wm831x_auxadc_read: Read a value from the WM831x AUXADC + * + * @wm831x: Device to read from. + * @input: AUXADC input to read. + */ +int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input) +{ + int tries = 10; + int ret, src; + + mutex_lock(&wm831x->auxadc_lock); + + ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, + WM831X_AUX_ENA, WM831X_AUX_ENA); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to enable AUXADC: %d\n", ret); + goto out; + } + + /* We force a single source at present */ + src = input; + ret = wm831x_reg_write(wm831x, WM831X_AUXADC_SOURCE, + 1 << src); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to set AUXADC source: %d\n", ret); + goto out; + } + + ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, + WM831X_AUX_CVT_ENA, WM831X_AUX_CVT_ENA); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to start AUXADC: %d\n", ret); + goto disable; + } + + do { + msleep(1); + + ret = wm831x_reg_read(wm831x, WM831X_AUXADC_CONTROL); + if (ret < 0) + ret = WM831X_AUX_CVT_ENA; + } while ((ret & WM831X_AUX_CVT_ENA) && --tries); + + if (ret & WM831X_AUX_CVT_ENA) { + dev_err(wm831x->dev, "Timed out reading AUXADC\n"); + ret = -EBUSY; + goto disable; + } + + ret = wm831x_reg_read(wm831x, WM831X_AUXADC_DATA); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read AUXADC data: %d\n", ret); + } else { + src = ((ret & WM831X_AUX_DATA_SRC_MASK) + >> WM831X_AUX_DATA_SRC_SHIFT) - 1; + + if (src == 14) + src = WM831X_AUX_CAL; + + if (src != input) { + dev_err(wm831x->dev, "Data from source %d not %d\n", + src, input); + ret = -EINVAL; + } else { + ret &= WM831X_AUX_DATA_MASK; + } + } + +disable: + wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, WM831X_AUX_ENA, 0); +out: + mutex_unlock(&wm831x->auxadc_lock); + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_auxadc_read); + +/** + * wm831x_auxadc_read_uv: Read a voltage from the WM831x AUXADC + * + * @wm831x: Device to read from. + * @input: AUXADC input to read. + */ +int wm831x_auxadc_read_uv(struct wm831x *wm831x, enum wm831x_auxadc input) +{ + int ret; + + ret = wm831x_auxadc_read(wm831x, input); + if (ret < 0) + return ret; + + ret *= 1465; + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_auxadc_read_uv); + +static struct resource wm831x_dcdc1_resources[] = { + { + .start = WM831X_DC1_CONTROL_1, + .end = WM831X_DC1_DVS_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC1, + .end = WM831X_IRQ_UV_DC1, + .flags = IORESOURCE_IRQ, + }, + { + .name = "HC", + .start = WM831X_IRQ_HC_DC1, + .end = WM831X_IRQ_HC_DC1, + .flags = IORESOURCE_IRQ, + }, +}; + + +static struct resource wm831x_dcdc2_resources[] = { + { + .start = WM831X_DC2_CONTROL_1, + .end = WM831X_DC2_DVS_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC2, + .end = WM831X_IRQ_UV_DC2, + .flags = IORESOURCE_IRQ, + }, + { + .name = "HC", + .start = WM831X_IRQ_HC_DC2, + .end = WM831X_IRQ_HC_DC2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_dcdc3_resources[] = { + { + .start = WM831X_DC3_CONTROL_1, + .end = WM831X_DC3_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC3, + .end = WM831X_IRQ_UV_DC3, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_dcdc4_resources[] = { + { + .start = WM831X_DC4_CONTROL, + .end = WM831X_DC4_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC4, + .end = WM831X_IRQ_UV_DC4, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm8320_dcdc4_buck_resources[] = { + { + .start = WM831X_DC4_CONTROL, + .end = WM832X_DC4_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC4, + .end = WM831X_IRQ_UV_DC4, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_gpio_resources[] = { + { + .start = WM831X_IRQ_GPIO_1, + .end = WM831X_IRQ_GPIO_16, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_isink1_resources[] = { + { + .start = WM831X_CURRENT_SINK_1, + .end = WM831X_CURRENT_SINK_1, + .flags = IORESOURCE_IO, + }, + { + .start = WM831X_IRQ_CS1, + .end = WM831X_IRQ_CS1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_isink2_resources[] = { + { + .start = WM831X_CURRENT_SINK_2, + .end = WM831X_CURRENT_SINK_2, + .flags = IORESOURCE_IO, + }, + { + .start = WM831X_IRQ_CS2, + .end = WM831X_IRQ_CS2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo1_resources[] = { + { + .start = WM831X_LDO1_CONTROL, + .end = WM831X_LDO1_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO1, + .end = WM831X_IRQ_UV_LDO1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo2_resources[] = { + { + .start = WM831X_LDO2_CONTROL, + .end = WM831X_LDO2_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO2, + .end = WM831X_IRQ_UV_LDO2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo3_resources[] = { + { + .start = WM831X_LDO3_CONTROL, + .end = WM831X_LDO3_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO3, + .end = WM831X_IRQ_UV_LDO3, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo4_resources[] = { + { + .start = WM831X_LDO4_CONTROL, + .end = WM831X_LDO4_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO4, + .end = WM831X_IRQ_UV_LDO4, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo5_resources[] = { + { + .start = WM831X_LDO5_CONTROL, + .end = WM831X_LDO5_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO5, + .end = WM831X_IRQ_UV_LDO5, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo6_resources[] = { + { + .start = WM831X_LDO6_CONTROL, + .end = WM831X_LDO6_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO6, + .end = WM831X_IRQ_UV_LDO6, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo7_resources[] = { + { + .start = WM831X_LDO7_CONTROL, + .end = WM831X_LDO7_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO7, + .end = WM831X_IRQ_UV_LDO7, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo8_resources[] = { + { + .start = WM831X_LDO8_CONTROL, + .end = WM831X_LDO8_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO8, + .end = WM831X_IRQ_UV_LDO8, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo9_resources[] = { + { + .start = WM831X_LDO9_CONTROL, + .end = WM831X_LDO9_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO9, + .end = WM831X_IRQ_UV_LDO9, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo10_resources[] = { + { + .start = WM831X_LDO10_CONTROL, + .end = WM831X_LDO10_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO10, + .end = WM831X_IRQ_UV_LDO10, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo11_resources[] = { + { + .start = WM831X_LDO11_ON_CONTROL, + .end = WM831X_LDO11_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource wm831x_on_resources[] = { + { + .start = WM831X_IRQ_ON, + .end = WM831X_IRQ_ON, + .flags = IORESOURCE_IRQ, + }, +}; + + +static struct resource wm831x_power_resources[] = { + { + .name = "SYSLO", + .start = WM831X_IRQ_PPM_SYSLO, + .end = WM831X_IRQ_PPM_SYSLO, + .flags = IORESOURCE_IRQ, + }, + { + .name = "PWR SRC", + .start = WM831X_IRQ_PPM_PWR_SRC, + .end = WM831X_IRQ_PPM_PWR_SRC, + .flags = IORESOURCE_IRQ, + }, + { + .name = "USB CURR", + .start = WM831X_IRQ_PPM_USB_CURR, + .end = WM831X_IRQ_PPM_USB_CURR, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT HOT", + .start = WM831X_IRQ_CHG_BATT_HOT, + .end = WM831X_IRQ_CHG_BATT_HOT, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT COLD", + .start = WM831X_IRQ_CHG_BATT_COLD, + .end = WM831X_IRQ_CHG_BATT_COLD, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT FAIL", + .start = WM831X_IRQ_CHG_BATT_FAIL, + .end = WM831X_IRQ_CHG_BATT_FAIL, + .flags = IORESOURCE_IRQ, + }, + { + .name = "OV", + .start = WM831X_IRQ_CHG_OV, + .end = WM831X_IRQ_CHG_OV, + .flags = IORESOURCE_IRQ, + }, + { + .name = "END", + .start = WM831X_IRQ_CHG_END, + .end = WM831X_IRQ_CHG_END, + .flags = IORESOURCE_IRQ, + }, + { + .name = "TO", + .start = WM831X_IRQ_CHG_TO, + .end = WM831X_IRQ_CHG_TO, + .flags = IORESOURCE_IRQ, + }, + { + .name = "MODE", + .start = WM831X_IRQ_CHG_MODE, + .end = WM831X_IRQ_CHG_MODE, + .flags = IORESOURCE_IRQ, + }, + { + .name = "START", + .start = WM831X_IRQ_CHG_START, + .end = WM831X_IRQ_CHG_START, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_rtc_resources[] = { + { + .name = "PER", + .start = WM831X_IRQ_RTC_PER, + .end = WM831X_IRQ_RTC_PER, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ALM", + .start = WM831X_IRQ_RTC_ALM, + .end = WM831X_IRQ_RTC_ALM, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_status1_resources[] = { + { + .start = WM831X_STATUS_LED_1, + .end = WM831X_STATUS_LED_1, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource wm831x_status2_resources[] = { + { + .start = WM831X_STATUS_LED_2, + .end = WM831X_STATUS_LED_2, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource wm831x_touch_resources[] = { + { + .name = "TCHPD", + .start = WM831X_IRQ_TCHPD, + .end = WM831X_IRQ_TCHPD, + .flags = IORESOURCE_IRQ, + }, + { + .name = "TCHDATA", + .start = WM831X_IRQ_TCHDATA, + .end = WM831X_IRQ_TCHDATA, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_wdt_resources[] = { + { + .start = WM831X_IRQ_WDOG_TO, + .end = WM831X_IRQ_WDOG_TO, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell wm8310_devs[] = { + { + .name = "wm831x-backup", + }, + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-boostp", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources), + .resources = wm831x_dcdc4_resources, + }, + { + .name = "wm831x-epe", + .id = 1, + }, + { + .name = "wm831x-epe", + .id = 2, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-isink", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_isink1_resources), + .resources = wm831x_isink1_resources, + }, + { + .name = "wm831x-isink", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_isink2_resources), + .resources = wm831x_isink2_resources, + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-ldo", + .id = 6, + .num_resources = ARRAY_SIZE(wm831x_ldo6_resources), + .resources = wm831x_ldo6_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-aldo", + .id = 8, + .num_resources = ARRAY_SIZE(wm831x_ldo8_resources), + .resources = wm831x_ldo8_resources, + }, + { + .name = "wm831x-aldo", + .id = 9, + .num_resources = ARRAY_SIZE(wm831x_ldo9_resources), + .resources = wm831x_ldo9_resources, + }, + { + .name = "wm831x-aldo", + .id = 10, + .num_resources = ARRAY_SIZE(wm831x_ldo10_resources), + .resources = wm831x_ldo10_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-power", + .num_resources = ARRAY_SIZE(wm831x_power_resources), + .resources = wm831x_power_resources, + }, + { + .name = "wm831x-rtc", + .num_resources = ARRAY_SIZE(wm831x_rtc_resources), + .resources = wm831x_rtc_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static struct mfd_cell wm8311_devs[] = { + { + .name = "wm831x-backup", + }, + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-boostp", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources), + .resources = wm831x_dcdc4_resources, + }, + { + .name = "wm831x-epe", + .id = 1, + }, + { + .name = "wm831x-epe", + .id = 2, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-isink", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_isink1_resources), + .resources = wm831x_isink1_resources, + }, + { + .name = "wm831x-isink", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_isink2_resources), + .resources = wm831x_isink2_resources, + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-power", + .num_resources = ARRAY_SIZE(wm831x_power_resources), + .resources = wm831x_power_resources, + }, + { + .name = "wm831x-rtc", + .num_resources = ARRAY_SIZE(wm831x_rtc_resources), + .resources = wm831x_rtc_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-touch", + .num_resources = ARRAY_SIZE(wm831x_touch_resources), + .resources = wm831x_touch_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static struct mfd_cell wm8312_devs[] = { + { + .name = "wm831x-backup", + }, + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-boostp", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources), + .resources = wm831x_dcdc4_resources, + }, + { + .name = "wm831x-epe", + .id = 1, + }, + { + .name = "wm831x-epe", + .id = 2, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-isink", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_isink1_resources), + .resources = wm831x_isink1_resources, + }, + { + .name = "wm831x-isink", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_isink2_resources), + .resources = wm831x_isink2_resources, + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-ldo", + .id = 6, + .num_resources = ARRAY_SIZE(wm831x_ldo6_resources), + .resources = wm831x_ldo6_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-aldo", + .id = 8, + .num_resources = ARRAY_SIZE(wm831x_ldo8_resources), + .resources = wm831x_ldo8_resources, + }, + { + .name = "wm831x-aldo", + .id = 9, + .num_resources = ARRAY_SIZE(wm831x_ldo9_resources), + .resources = wm831x_ldo9_resources, + }, + { + .name = "wm831x-aldo", + .id = 10, + .num_resources = ARRAY_SIZE(wm831x_ldo10_resources), + .resources = wm831x_ldo10_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-power", + .num_resources = ARRAY_SIZE(wm831x_power_resources), + .resources = wm831x_power_resources, + }, + { + .name = "wm831x-rtc", + .num_resources = ARRAY_SIZE(wm831x_rtc_resources), + .resources = wm831x_rtc_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-touch", + .num_resources = ARRAY_SIZE(wm831x_touch_resources), + .resources = wm831x_touch_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static struct mfd_cell wm8320_devs[] = { + { + .name = "wm831x-backup", + }, + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-buckp", + .id = 4, + .num_resources = ARRAY_SIZE(wm8320_dcdc4_buck_resources), + .resources = wm8320_dcdc4_buck_resources, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-ldo", + .id = 6, + .num_resources = ARRAY_SIZE(wm831x_ldo6_resources), + .resources = wm831x_ldo6_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-aldo", + .id = 8, + .num_resources = ARRAY_SIZE(wm831x_ldo8_resources), + .resources = wm831x_ldo8_resources, + }, + { + .name = "wm831x-aldo", + .id = 9, + .num_resources = ARRAY_SIZE(wm831x_ldo9_resources), + .resources = wm831x_ldo9_resources, + }, + { + .name = "wm831x-aldo", + .id = 10, + .num_resources = ARRAY_SIZE(wm831x_ldo10_resources), + .resources = wm831x_ldo10_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-rtc", + .num_resources = ARRAY_SIZE(wm831x_rtc_resources), + .resources = wm831x_rtc_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static struct mfd_cell backlight_devs[] = { + { + .name = "wm831x-backlight", + }, +}; + +/* + * Instantiate the generic non-control parts of the device. + */ +static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) +{ + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int rev; + enum wm831x_parent parent; + int ret; + + mutex_init(&wm831x->io_lock); + mutex_init(&wm831x->key_lock); + mutex_init(&wm831x->auxadc_lock); + dev_set_drvdata(wm831x->dev, wm831x); + + ret = wm831x_reg_read(wm831x, WM831X_PARENT_ID); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read parent ID: %d\n", ret); + goto err; + } + if (ret != 0x6204) { + dev_err(wm831x->dev, "Device is not a WM831x: ID %x\n", ret); + ret = -EINVAL; + goto err; + } + + ret = wm831x_reg_read(wm831x, WM831X_REVISION); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read revision: %d\n", ret); + goto err; + } + rev = (ret & WM831X_PARENT_REV_MASK) >> WM831X_PARENT_REV_SHIFT; + + ret = wm831x_reg_read(wm831x, WM831X_RESET_ID); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read device ID: %d\n", ret); + goto err; + } + + /* Some engineering samples do not have the ID set, rely on + * the device being registered correctly. + */ + if (ret == 0) { + dev_info(wm831x->dev, "Device is an engineering sample\n"); + ret = id; + } + + switch (ret) { + case WM8310: + parent = WM8310; + wm831x->num_gpio = 16; + dev_info(wm831x->dev, "WM8310 revision %c\n", 'A' + rev); + break; + + case WM8311: + parent = WM8311; + wm831x->num_gpio = 16; + dev_info(wm831x->dev, "WM8311 revision %c\n", 'A' + rev); + break; + + case WM8312: + parent = WM8312; + wm831x->num_gpio = 16; + dev_info(wm831x->dev, "WM8312 revision %c\n", 'A' + rev); + break; + + case WM8320: + parent = WM8320; + wm831x->num_gpio = 12; + dev_info(wm831x->dev, "WM8320 revision %c\n", 'A' + rev); + break; + + default: + dev_err(wm831x->dev, "Unknown WM831x device %04x\n", ret); + ret = -EINVAL; + goto err; + } + + /* This will need revisiting in future but is OK for all + * current parts. + */ + if (parent != id) + dev_warn(wm831x->dev, "Device was registered as a WM%lx\n", + id); + + /* Bootstrap the user key */ + ret = wm831x_reg_read(wm831x, WM831X_SECURITY_KEY); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read security key: %d\n", ret); + goto err; + } + if (ret != 0) { + dev_warn(wm831x->dev, "Security key had non-zero value %x\n", + ret); + wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0); + } + wm831x->locked = 1; + + if (pdata && pdata->pre_init) { + ret = pdata->pre_init(wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "pre_init() failed: %d\n", ret); + goto err; + } + } + + ret = wm831x_irq_init(wm831x, irq); + if (ret != 0) + goto err; + + /* The core device is up, instantiate the subdevices. */ + switch (parent) { + case WM8310: + ret = mfd_add_devices(wm831x->dev, -1, + wm8310_devs, ARRAY_SIZE(wm8310_devs), + NULL, wm831x->irq_base); + break; + + case WM8311: + ret = mfd_add_devices(wm831x->dev, -1, + wm8311_devs, ARRAY_SIZE(wm8311_devs), + NULL, wm831x->irq_base); + break; + + case WM8312: + ret = mfd_add_devices(wm831x->dev, -1, + wm8312_devs, ARRAY_SIZE(wm8312_devs), + NULL, wm831x->irq_base); + break; + + case WM8320: + ret = mfd_add_devices(wm831x->dev, -1, + wm8320_devs, ARRAY_SIZE(wm8320_devs), + NULL, 0); + break; + + default: + /* If this happens the bus probe function is buggy */ + BUG(); + } + + if (ret != 0) { + dev_err(wm831x->dev, "Failed to add children\n"); + goto err_irq; + } + + if (pdata && pdata->backlight) { + /* Treat errors as non-critical */ + ret = mfd_add_devices(wm831x->dev, -1, backlight_devs, + ARRAY_SIZE(backlight_devs), NULL, + wm831x->irq_base); + if (ret < 0) + dev_err(wm831x->dev, "Failed to add backlight: %d\n", + ret); + } + + wm831x_otp_init(wm831x); + + if (pdata && pdata->post_init) { + ret = pdata->post_init(wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "post_init() failed: %d\n", ret); + goto err_irq; + } + } + + return 0; + +err_irq: + wm831x_irq_exit(wm831x); +err: + mfd_remove_devices(wm831x->dev); + kfree(wm831x); + return ret; +} + +static void wm831x_device_exit(struct wm831x *wm831x) +{ + wm831x_otp_exit(wm831x); + mfd_remove_devices(wm831x->dev); + wm831x_irq_exit(wm831x); + kfree(wm831x); +} + +static int wm831x_i2c_read_device(struct wm831x *wm831x, unsigned short reg, + int bytes, void *dest) +{ + struct i2c_client *i2c = wm831x->control_data; + int ret; + u16 r = cpu_to_be16(reg); + + ret = i2c_master_send(i2c, (unsigned char *)&r, 2); + if (ret < 0) + return ret; + if (ret != 2) + return -EIO; + + ret = i2c_master_recv(i2c, dest, bytes); + if (ret < 0) + return ret; + if (ret != bytes) + return -EIO; + return 0; +} + +/* Currently we allocate the write buffer on the stack; this is OK for + * small writes - if we need to do large writes this will need to be + * revised. + */ +static int wm831x_i2c_write_device(struct wm831x *wm831x, unsigned short reg, + int bytes, void *src) +{ + struct i2c_client *i2c = wm831x->control_data; + unsigned char msg[bytes + 2]; + int ret; + + reg = cpu_to_be16(reg); + memcpy(&msg[0], ®, 2); + memcpy(&msg[2], src, bytes); + + ret = i2c_master_send(i2c, msg, bytes + 2); + if (ret < 0) + return ret; + if (ret < bytes + 2) + return -EIO; + + return 0; +} + +static int wm831x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm831x *wm831x; + + wm831x = kzalloc(sizeof(struct wm831x), GFP_KERNEL); + if (wm831x == NULL) { + kfree(i2c); + return -ENOMEM; + } + + i2c_set_clientdata(i2c, wm831x); + wm831x->dev = &i2c->dev; + wm831x->control_data = i2c; + wm831x->read_dev = wm831x_i2c_read_device; + wm831x->write_dev = wm831x_i2c_write_device; + + return wm831x_device_init(wm831x, id->driver_data, i2c->irq); +} + +static int wm831x_i2c_remove(struct i2c_client *i2c) +{ + struct wm831x *wm831x = i2c_get_clientdata(i2c); + + wm831x_device_exit(wm831x); + + return 0; +} + +static const struct i2c_device_id wm831x_i2c_id[] = { + { "wm8310", WM8310 }, + { "wm8311", WM8311 }, + { "wm8312", WM8312 }, + { "wm8320", WM8320 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm831x_i2c_id); + + +static struct i2c_driver wm831x_i2c_driver = { + .driver = { + .name = "wm831x", + .owner = THIS_MODULE, + }, + .probe = wm831x_i2c_probe, + .remove = wm831x_i2c_remove, + .id_table = wm831x_i2c_id, +}; + +static int __init wm831x_i2c_init(void) +{ + int ret; + + ret = i2c_add_driver(&wm831x_i2c_driver); + if (ret != 0) + pr_err("Failed to register wm831x I2C driver: %d\n", ret); + + return ret; +} +subsys_initcall(wm831x_i2c_init); + +static void __exit wm831x_i2c_exit(void) +{ + i2c_del_driver(&wm831x_i2c_driver); +} +module_exit(wm831x_i2c_exit); + +MODULE_DESCRIPTION("I2C support for the WM831X AudioPlus PMIC"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Brown"); diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c new file mode 100644 index 00000000000..30132769711 --- /dev/null +++ b/drivers/mfd/wm831x-irq.c @@ -0,0 +1,525 @@ +/* + * wm831x-irq.c -- Interrupt controller support for Wolfson WM831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/mfd/core.h> +#include <linux/interrupt.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/pdata.h> +#include <linux/mfd/wm831x/irq.h> + +#include <linux/delay.h> + +/* + * Since generic IRQs don't currently support interrupt controllers on + * interrupt driven buses we don't use genirq but instead provide an + * interface that looks very much like the standard ones. This leads + * to some bodges, including storing interrupt handler information in + * the static irq_data table we use to look up the data for individual + * interrupts, but hopefully won't last too long. + */ + +struct wm831x_irq_data { + int primary; + int reg; + int mask; + irq_handler_t handler; + void *handler_data; +}; + +static struct wm831x_irq_data wm831x_irqs[] = { + [WM831X_IRQ_TEMP_THW] = { + .primary = WM831X_TEMP_INT, + .reg = 1, + .mask = WM831X_TEMP_THW_EINT, + }, + [WM831X_IRQ_GPIO_1] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP1_EINT, + }, + [WM831X_IRQ_GPIO_2] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP2_EINT, + }, + [WM831X_IRQ_GPIO_3] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP3_EINT, + }, + [WM831X_IRQ_GPIO_4] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP4_EINT, + }, + [WM831X_IRQ_GPIO_5] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP5_EINT, + }, + [WM831X_IRQ_GPIO_6] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP6_EINT, + }, + [WM831X_IRQ_GPIO_7] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP7_EINT, + }, + [WM831X_IRQ_GPIO_8] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP8_EINT, + }, + [WM831X_IRQ_GPIO_9] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP9_EINT, + }, + [WM831X_IRQ_GPIO_10] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP10_EINT, + }, + [WM831X_IRQ_GPIO_11] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP11_EINT, + }, + [WM831X_IRQ_GPIO_12] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP12_EINT, + }, + [WM831X_IRQ_GPIO_13] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP13_EINT, + }, + [WM831X_IRQ_GPIO_14] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP14_EINT, + }, + [WM831X_IRQ_GPIO_15] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP15_EINT, + }, + [WM831X_IRQ_GPIO_16] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP16_EINT, + }, + [WM831X_IRQ_ON] = { + .primary = WM831X_ON_PIN_INT, + .reg = 1, + .mask = WM831X_ON_PIN_EINT, + }, + [WM831X_IRQ_PPM_SYSLO] = { + .primary = WM831X_PPM_INT, + .reg = 1, + .mask = WM831X_PPM_SYSLO_EINT, + }, + [WM831X_IRQ_PPM_PWR_SRC] = { + .primary = WM831X_PPM_INT, + .reg = 1, + .mask = WM831X_PPM_PWR_SRC_EINT, + }, + [WM831X_IRQ_PPM_USB_CURR] = { + .primary = WM831X_PPM_INT, + .reg = 1, + .mask = WM831X_PPM_USB_CURR_EINT, + }, + [WM831X_IRQ_WDOG_TO] = { + .primary = WM831X_WDOG_INT, + .reg = 1, + .mask = WM831X_WDOG_TO_EINT, + }, + [WM831X_IRQ_RTC_PER] = { + .primary = WM831X_RTC_INT, + .reg = 1, + .mask = WM831X_RTC_PER_EINT, + }, + [WM831X_IRQ_RTC_ALM] = { + .primary = WM831X_RTC_INT, + .reg = 1, + .mask = WM831X_RTC_ALM_EINT, + }, + [WM831X_IRQ_CHG_BATT_HOT] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_BATT_HOT_EINT, + }, + [WM831X_IRQ_CHG_BATT_COLD] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_BATT_COLD_EINT, + }, + [WM831X_IRQ_CHG_BATT_FAIL] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_BATT_FAIL_EINT, + }, + [WM831X_IRQ_CHG_OV] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_OV_EINT, + }, + [WM831X_IRQ_CHG_END] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_END_EINT, + }, + [WM831X_IRQ_CHG_TO] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_TO_EINT, + }, + [WM831X_IRQ_CHG_MODE] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_MODE_EINT, + }, + [WM831X_IRQ_CHG_START] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_START_EINT, + }, + [WM831X_IRQ_TCHDATA] = { + .primary = WM831X_TCHDATA_INT, + .reg = 1, + .mask = WM831X_TCHDATA_EINT, + }, + [WM831X_IRQ_TCHPD] = { + .primary = WM831X_TCHPD_INT, + .reg = 1, + .mask = WM831X_TCHPD_EINT, + }, + [WM831X_IRQ_AUXADC_DATA] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DATA_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP1] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP1_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP2] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP2_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP3] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP3_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP4] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP4_EINT, + }, + [WM831X_IRQ_CS1] = { + .primary = WM831X_CS_INT, + .reg = 2, + .mask = WM831X_CS1_EINT, + }, + [WM831X_IRQ_CS2] = { + .primary = WM831X_CS_INT, + .reg = 2, + .mask = WM831X_CS2_EINT, + }, + [WM831X_IRQ_HC_DC1] = { + .primary = WM831X_HC_INT, + .reg = 4, + .mask = WM831X_HC_DC1_EINT, + }, + [WM831X_IRQ_HC_DC2] = { + .primary = WM831X_HC_INT, + .reg = 4, + .mask = WM831X_HC_DC2_EINT, + }, + [WM831X_IRQ_UV_LDO1] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO1_EINT, + }, + [WM831X_IRQ_UV_LDO2] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO2_EINT, + }, + [WM831X_IRQ_UV_LDO3] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO3_EINT, + }, + [WM831X_IRQ_UV_LDO4] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO4_EINT, + }, + [WM831X_IRQ_UV_LDO5] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO5_EINT, + }, + [WM831X_IRQ_UV_LDO6] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO6_EINT, + }, + [WM831X_IRQ_UV_LDO7] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO7_EINT, + }, + [WM831X_IRQ_UV_LDO8] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO8_EINT, + }, + [WM831X_IRQ_UV_LDO9] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO9_EINT, + }, + [WM831X_IRQ_UV_LDO10] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO10_EINT, + }, + [WM831X_IRQ_UV_DC1] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC1_EINT, + }, + [WM831X_IRQ_UV_DC2] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC2_EINT, + }, + [WM831X_IRQ_UV_DC3] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC3_EINT, + }, + [WM831X_IRQ_UV_DC4] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC4_EINT, + }, +}; + +static inline int irq_data_to_status_reg(struct wm831x_irq_data *irq_data) +{ + return WM831X_INTERRUPT_STATUS_1 - 1 + irq_data->reg; +} + +static inline int irq_data_to_mask_reg(struct wm831x_irq_data *irq_data) +{ + return WM831X_INTERRUPT_STATUS_1_MASK - 1 + irq_data->reg; +} + +static inline struct wm831x_irq_data *irq_to_wm831x_irq(struct wm831x *wm831x, + int irq) +{ + return &wm831x_irqs[irq - wm831x->irq_base]; +} + +static void wm831x_irq_lock(unsigned int irq) +{ + struct wm831x *wm831x = get_irq_chip_data(irq); + + mutex_lock(&wm831x->irq_lock); +} + +static void wm831x_irq_sync_unlock(unsigned int irq) +{ + struct wm831x *wm831x = get_irq_chip_data(irq); + int i; + + for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks_cur); i++) { + /* If there's been a change in the mask write it back + * to the hardware. */ + if (wm831x->irq_masks_cur[i] != wm831x->irq_masks_cache[i]) { + wm831x->irq_masks_cache[i] = wm831x->irq_masks_cur[i]; + wm831x_reg_write(wm831x, + WM831X_INTERRUPT_STATUS_1_MASK + i, + wm831x->irq_masks_cur[i]); + } + } + + mutex_unlock(&wm831x->irq_lock); +} + +static void wm831x_irq_unmask(unsigned int irq) +{ + struct wm831x *wm831x = get_irq_chip_data(irq); + struct wm831x_irq_data *irq_data = irq_to_wm831x_irq(wm831x, irq); + + wm831x->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask; +} + +static void wm831x_irq_mask(unsigned int irq) +{ + struct wm831x *wm831x = get_irq_chip_data(irq); + struct wm831x_irq_data *irq_data = irq_to_wm831x_irq(wm831x, irq); + + wm831x->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask; +} + +static struct irq_chip wm831x_irq_chip = { + .name = "wm831x", + .bus_lock = wm831x_irq_lock, + .bus_sync_unlock = wm831x_irq_sync_unlock, + .mask = wm831x_irq_mask, + .unmask = wm831x_irq_unmask, +}; + +/* The processing of the primary interrupt occurs in a thread so that + * we can interact with the device over I2C or SPI. */ +static irqreturn_t wm831x_irq_thread(int irq, void *data) +{ + struct wm831x *wm831x = data; + unsigned int i; + int primary; + int status_regs[WM831X_NUM_IRQ_REGS] = { 0 }; + int read[WM831X_NUM_IRQ_REGS] = { 0 }; + int *status; + + primary = wm831x_reg_read(wm831x, WM831X_SYSTEM_INTERRUPTS); + if (primary < 0) { + dev_err(wm831x->dev, "Failed to read system interrupt: %d\n", + primary); + goto out; + } + + for (i = 0; i < ARRAY_SIZE(wm831x_irqs); i++) { + int offset = wm831x_irqs[i].reg - 1; + + if (!(primary & wm831x_irqs[i].primary)) + continue; + + status = &status_regs[offset]; + + /* Hopefully there should only be one register to read + * each time otherwise we ought to do a block read. */ + if (!read[offset]) { + *status = wm831x_reg_read(wm831x, + irq_data_to_status_reg(&wm831x_irqs[i])); + if (*status < 0) { + dev_err(wm831x->dev, + "Failed to read IRQ status: %d\n", + *status); + goto out; + } + + read[offset] = 1; + } + + /* Report it if it isn't masked, or forget the status. */ + if ((*status & ~wm831x->irq_masks_cur[offset]) + & wm831x_irqs[i].mask) + handle_nested_irq(wm831x->irq_base + i); + else + *status &= ~wm831x_irqs[i].mask; + } + +out: + for (i = 0; i < ARRAY_SIZE(status_regs); i++) { + if (status_regs[i]) + wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1 + i, + status_regs[i]); + } + + return IRQ_HANDLED; +} + +int wm831x_irq_init(struct wm831x *wm831x, int irq) +{ + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int i, cur_irq, ret; + + mutex_init(&wm831x->irq_lock); + + if (!irq) { + dev_warn(wm831x->dev, + "No interrupt specified - functionality limited\n"); + return 0; + } + + if (!pdata || !pdata->irq_base) { + dev_err(wm831x->dev, + "No interrupt base specified, no interrupts\n"); + return 0; + } + + wm831x->irq = irq; + wm831x->irq_base = pdata->irq_base; + + /* Mask the individual interrupt sources */ + for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks_cur); i++) { + wm831x->irq_masks_cur[i] = 0xffff; + wm831x->irq_masks_cache[i] = 0xffff; + wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1_MASK + i, + 0xffff); + } + + /* Register them with genirq */ + for (cur_irq = wm831x->irq_base; + cur_irq < ARRAY_SIZE(wm831x_irqs) + wm831x->irq_base; + cur_irq++) { + set_irq_chip_data(cur_irq, wm831x); + set_irq_chip_and_handler(cur_irq, &wm831x_irq_chip, + handle_edge_irq); + set_irq_nested_thread(cur_irq, 1); + + /* ARM needs us to explicitly flag the IRQ as valid + * and will set them noprobe when we do so. */ +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + set_irq_noprobe(cur_irq); +#endif + } + + ret = request_threaded_irq(irq, NULL, wm831x_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "wm831x", wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "Failed to request IRQ %d: %d\n", + irq, ret); + return ret; + } + + /* Enable top level interrupts, we mask at secondary level */ + wm831x_reg_write(wm831x, WM831X_SYSTEM_INTERRUPTS_MASK, 0); + + return 0; +} + +void wm831x_irq_exit(struct wm831x *wm831x) +{ + if (wm831x->irq) + free_irq(wm831x->irq, wm831x); +} diff --git a/drivers/mfd/wm831x-otp.c b/drivers/mfd/wm831x-otp.c new file mode 100644 index 00000000000..f742745ff35 --- /dev/null +++ b/drivers/mfd/wm831x-otp.c @@ -0,0 +1,83 @@ +/* + * wm831x-otp.c -- OTP for Wolfson WM831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/bcd.h> +#include <linux/delay.h> +#include <linux/mfd/core.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/otp.h> + +/* In bytes */ +#define WM831X_UNIQUE_ID_LEN 16 + +/* Read the unique ID from the chip into id */ +static int wm831x_unique_id_read(struct wm831x *wm831x, char *id) +{ + int i, val; + + for (i = 0; i < WM831X_UNIQUE_ID_LEN / 2; i++) { + val = wm831x_reg_read(wm831x, WM831X_UNIQUE_ID_1 + i); + if (val < 0) + return val; + + id[i * 2] = (val >> 8) & 0xff; + id[(i * 2) + 1] = val & 0xff; + } + + return 0; +} + +static ssize_t wm831x_unique_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm831x *wm831x = dev_get_drvdata(dev); + int i, rval; + char id[WM831X_UNIQUE_ID_LEN]; + ssize_t ret = 0; + + rval = wm831x_unique_id_read(wm831x, id); + if (rval < 0) + return 0; + + for (i = 0; i < WM831X_UNIQUE_ID_LEN; i++) + ret += sprintf(&buf[ret], "%02x", buf[i]); + + ret += sprintf(&buf[ret], "\n"); + + return ret; +} + +static DEVICE_ATTR(unique_id, 0444, wm831x_unique_id_show, NULL); + +int wm831x_otp_init(struct wm831x *wm831x) +{ + int ret; + + ret = device_create_file(wm831x->dev, &dev_attr_unique_id); + if (ret != 0) + dev_err(wm831x->dev, "Unique ID attribute not created: %d\n", + ret); + + return ret; +} + +void wm831x_otp_exit(struct wm831x *wm831x) +{ + device_remove_file(wm831x->dev, &dev_attr_unique_id); +} + diff --git a/drivers/mfd/wm8350-core.c b/drivers/mfd/wm8350-core.c new file mode 100644 index 00000000000..9a970bd6877 --- /dev/null +++ b/drivers/mfd/wm8350-core.c @@ -0,0 +1,746 @@ +/* + * wm8350-core.c -- Device access for Wolfson WM8350 + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood, Mark Brown + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/bug.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> + +#include <linux/mfd/wm8350/core.h> +#include <linux/mfd/wm8350/audio.h> +#include <linux/mfd/wm8350/comparator.h> +#include <linux/mfd/wm8350/gpio.h> +#include <linux/mfd/wm8350/pmic.h> +#include <linux/mfd/wm8350/rtc.h> +#include <linux/mfd/wm8350/supply.h> +#include <linux/mfd/wm8350/wdt.h> + +#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 dump(regs, src) do { \ + int i_; \ + u16 *src_ = src; \ + printk(KERN_DEBUG); \ + for (i_ = 0; i_ < regs; i_++) \ + printk(" 0x%4.4x", *src_++); \ + printk("\n"); \ +} while (0); +#else +#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 + +/* + * WM8350 Device IO + */ +static DEFINE_MUTEX(io_mutex); +static DEFINE_MUTEX(reg_lock_mutex); + +/* Perform a physical read from the device. + */ +static int wm8350_phys_read(struct wm8350 *wm8350, u8 reg, int num_regs, + u16 *dest) +{ + int i, ret; + int bytes = num_regs * 2; + + dev_dbg(wm8350->dev, "volatile read\n"); + ret = wm8350->read_dev(wm8350, reg, bytes, (char *)dest); + + for (i = reg; i < reg + num_regs; i++) { + /* Cache is CPU endian */ + dest[i - reg] = be16_to_cpu(dest[i - reg]); + + /* Mask out non-readable bits */ + dest[i - reg] &= wm8350_reg_io_map[i].readable; + } + + dump(num_regs, dest); + + return ret; +} + +static int wm8350_read(struct wm8350 *wm8350, u8 reg, int num_regs, u16 *dest) +{ + int i; + int end = reg + num_regs; + int ret = 0; + int bytes = num_regs * 2; + + if (wm8350->read_dev == NULL) + return -ENODEV; + + if ((reg + num_regs - 1) > WM8350_MAX_REGISTER) { + dev_err(wm8350->dev, "invalid reg %x\n", + reg + num_regs - 1); + return -EINVAL; + } + + dev_dbg(wm8350->dev, + "%s R%d(0x%2.2x) %d regs\n", __func__, 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) + dev_warn(wm8350->dev, + "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) + return wm8350_phys_read(wm8350, reg, num_regs, dest); + + /* no volatiles, then cache is good */ + dev_dbg(wm8350->dev, "cache read\n"); + 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_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 i; + int end = reg + num_regs; + int bytes = num_regs * 2; + + if (wm8350->write_dev == NULL) + return -ENODEV; + + if ((reg + num_regs - 1) > WM8350_MAX_REGISTER) { + dev_err(wm8350->dev, "invalid reg %x\n", + reg + num_regs - 1); + return -EINVAL; + } + + /* 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) { + dev_err(wm8350->dev, + "attempted write to read only reg R%d\n", i); + return -EINVAL; + } + + if (is_reg_locked(wm8350, i)) { + dev_err(wm8350->dev, + "attempted write to locked reg R%d\n", i); + return -EINVAL; + } + + src[i - reg] &= wm8350_reg_io_map[i].writable; + + wm8350->reg_cache[i] = + (wm8350->reg_cache[i] & ~wm8350_reg_io_map[i].writable) + | src[i - reg]; + + src[i - reg] = cpu_to_be16(src[i - reg]); + } + + /* Actually write it out */ + return wm8350->write_dev(wm8350, reg, bytes, (char *)src); +} + +/* + * 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) { + dev_err(wm8350->dev, "read from reg R%d failed\n", reg); + goto out; + } + + data &= ~mask; + err = wm8350_write(wm8350, reg, 1, &data); + if (err) + dev_err(wm8350->dev, "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) { + dev_err(wm8350->dev, "read from reg R%d failed\n", reg); + goto out; + } + + data |= mask; + err = wm8350_write(wm8350, reg, 1, &data); + if (err) + dev_err(wm8350->dev, "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) + dev_err(wm8350->dev, "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) + dev_err(wm8350->dev, "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) + dev_err(wm8350->dev, "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) + dev_err(wm8350->dev, "block write starting at R%d failed\n", + start_reg); + mutex_unlock(&io_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_block_write); + +/** + * wm8350_reg_lock() + * + * The WM8350 has a hardware lock which can be used to prevent writes to + * some registers (generally those which can cause particularly serious + * problems if misused). This function enables that lock. + */ +int wm8350_reg_lock(struct wm8350 *wm8350) +{ + u16 key = WM8350_LOCK_KEY; + int ret; + + ldbg(__func__); + mutex_lock(&io_mutex); + ret = wm8350_write(wm8350, WM8350_SECURITY, 1, &key); + if (ret) + dev_err(wm8350->dev, "lock failed\n"); + mutex_unlock(&io_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_reg_lock); + +/** + * wm8350_reg_unlock() + * + * The WM8350 has a hardware lock which can be used to prevent writes to + * some registers (generally those which can cause particularly serious + * problems if misused). This function disables that lock so updates + * can be performed. For maximum safety this should be done only when + * required. + */ +int wm8350_reg_unlock(struct wm8350 *wm8350) +{ + u16 key = WM8350_UNLOCK_KEY; + int ret; + + ldbg(__func__); + mutex_lock(&io_mutex); + ret = wm8350_write(wm8350, WM8350_SECURITY, 1, &key); + if (ret) + dev_err(wm8350->dev, "unlock failed\n"); + mutex_unlock(&io_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_reg_unlock); + +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(&wm8350->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 ((reg & WM8350_AUXADC_POLL) && --tries); + + if (!tries) + dev_err(wm8350->dev, "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(&wm8350->auxadc_mutex); + + return result & WM8350_AUXADC_DATA1_MASK; +} +EXPORT_SYMBOL_GPL(wm8350_read_auxadc); + +/* + * Cache is always host endian. + */ +static int wm8350_create_cache(struct wm8350 *wm8350, int type, int mode) +{ + int i, ret = 0; + u16 value; + const u16 *reg_map; + + switch (type) { + case 0: + switch (mode) { +#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_0 + case 0: + reg_map = wm8350_mode0_defaults; + break; +#endif +#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_1 + case 1: + reg_map = wm8350_mode1_defaults; + break; +#endif +#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_2 + case 2: + reg_map = wm8350_mode2_defaults; + break; +#endif +#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_3 + case 3: + reg_map = wm8350_mode3_defaults; + break; +#endif + default: + dev_err(wm8350->dev, + "WM8350 configuration mode %d not supported\n", + mode); + return -EINVAL; + } + break; + + case 1: + switch (mode) { +#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_0 + case 0: + reg_map = wm8351_mode0_defaults; + break; +#endif +#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_1 + case 1: + reg_map = wm8351_mode1_defaults; + break; +#endif +#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_2 + case 2: + reg_map = wm8351_mode2_defaults; + break; +#endif +#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_3 + case 3: + reg_map = wm8351_mode3_defaults; + break; +#endif + default: + dev_err(wm8350->dev, + "WM8351 configuration mode %d not supported\n", + mode); + return -EINVAL; + } + break; + + case 2: + switch (mode) { +#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_0 + case 0: + reg_map = wm8352_mode0_defaults; + break; +#endif +#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_1 + case 1: + reg_map = wm8352_mode1_defaults; + break; +#endif +#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_2 + case 2: + reg_map = wm8352_mode2_defaults; + break; +#endif +#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_3 + case 3: + reg_map = wm8352_mode3_defaults; + break; +#endif + default: + dev_err(wm8350->dev, + "WM8352 configuration mode %d not supported\n", + mode); + return -EINVAL; + } + break; + + default: + dev_err(wm8350->dev, + "WM835x configuration mode %d not supported\n", + mode); + return -EINVAL; + } + + wm8350->reg_cache = + kmalloc(sizeof(u16) * (WM8350_MAX_REGISTER + 1), GFP_KERNEL); + if (wm8350->reg_cache == NULL) + return -ENOMEM; + + /* Read the initial cache state back from the device - this is + * a PMIC so the device many not be in a virgin state and we + * can't rely on the silicon values. + */ + ret = wm8350->read_dev(wm8350, 0, + sizeof(u16) * (WM8350_MAX_REGISTER + 1), + wm8350->reg_cache); + if (ret < 0) { + dev_err(wm8350->dev, + "failed to read initial cache values\n"); + goto out; + } + + /* Mask out uncacheable/unreadable bits and the audio. */ + for (i = 0; i < WM8350_MAX_REGISTER; i++) { + if (wm8350_reg_io_map[i].readable && + (i < WM8350_CLOCK_CONTROL_1 || i > WM8350_AIF_TEST)) { + value = be16_to_cpu(wm8350->reg_cache[i]); + value &= wm8350_reg_io_map[i].readable; + wm8350->reg_cache[i] = value; + } else + wm8350->reg_cache[i] = reg_map[i]; + } + +out: + return ret; +} + +/* + * Register a client device. This is non-fatal since there is no need to + * fail the entire device init due to a single platform device failing. + */ +static void wm8350_client_dev_register(struct wm8350 *wm8350, + const char *name, + struct platform_device **pdev) +{ + int ret; + + *pdev = platform_device_alloc(name, -1); + if (*pdev == NULL) { + dev_err(wm8350->dev, "Failed to allocate %s\n", name); + return; + } + + (*pdev)->dev.parent = wm8350->dev; + platform_set_drvdata(*pdev, wm8350); + ret = platform_device_add(*pdev); + if (ret != 0) { + dev_err(wm8350->dev, "Failed to register %s: %d\n", name, ret); + platform_device_put(*pdev); + *pdev = NULL; + } +} + +int wm8350_device_init(struct wm8350 *wm8350, int irq, + struct wm8350_platform_data *pdata) +{ + int ret; + u16 id1, id2, mask_rev; + u16 cust_id, mode, chip_rev; + + /* get WM8350 revision and config mode */ + ret = wm8350->read_dev(wm8350, WM8350_RESET_ID, sizeof(id1), &id1); + if (ret != 0) { + dev_err(wm8350->dev, "Failed to read ID: %d\n", ret); + goto err; + } + + ret = wm8350->read_dev(wm8350, WM8350_ID, sizeof(id2), &id2); + if (ret != 0) { + dev_err(wm8350->dev, "Failed to read ID: %d\n", ret); + goto err; + } + + ret = wm8350->read_dev(wm8350, WM8350_REVISION, sizeof(mask_rev), + &mask_rev); + if (ret != 0) { + dev_err(wm8350->dev, "Failed to read revision: %d\n", ret); + goto err; + } + + id1 = be16_to_cpu(id1); + id2 = be16_to_cpu(id2); + mask_rev = be16_to_cpu(mask_rev); + + if (id1 != 0x6143) { + dev_err(wm8350->dev, + "Device with ID %x is not a WM8350\n", id1); + ret = -ENODEV; + goto err; + } + + mode = id2 & WM8350_CONF_STS_MASK >> 10; + cust_id = id2 & WM8350_CUST_ID_MASK; + chip_rev = (id2 & WM8350_CHIP_REV_MASK) >> 12; + dev_info(wm8350->dev, + "CONF_STS %d, CUST_ID %d, MASK_REV %d, CHIP_REV %d\n", + mode, cust_id, mask_rev, chip_rev); + + if (cust_id != 0) { + dev_err(wm8350->dev, "Unsupported CUST_ID\n"); + ret = -ENODEV; + goto err; + } + + switch (mask_rev) { + case 0: + wm8350->pmic.max_dcdc = WM8350_DCDC_6; + wm8350->pmic.max_isink = WM8350_ISINK_B; + + switch (chip_rev) { + case WM8350_REV_E: + dev_info(wm8350->dev, "WM8350 Rev E\n"); + break; + case WM8350_REV_F: + dev_info(wm8350->dev, "WM8350 Rev F\n"); + break; + case WM8350_REV_G: + dev_info(wm8350->dev, "WM8350 Rev G\n"); + wm8350->power.rev_g_coeff = 1; + break; + case WM8350_REV_H: + dev_info(wm8350->dev, "WM8350 Rev H\n"); + wm8350->power.rev_g_coeff = 1; + break; + default: + /* For safety we refuse to run on unknown hardware */ + dev_err(wm8350->dev, "Unknown WM8350 CHIP_REV\n"); + ret = -ENODEV; + goto err; + } + break; + + case 1: + wm8350->pmic.max_dcdc = WM8350_DCDC_4; + wm8350->pmic.max_isink = WM8350_ISINK_A; + + switch (chip_rev) { + case 0: + dev_info(wm8350->dev, "WM8351 Rev A\n"); + wm8350->power.rev_g_coeff = 1; + break; + + case 1: + dev_info(wm8350->dev, "WM8351 Rev B\n"); + wm8350->power.rev_g_coeff = 1; + break; + + default: + dev_err(wm8350->dev, "Unknown WM8351 CHIP_REV\n"); + ret = -ENODEV; + goto err; + } + break; + + case 2: + wm8350->pmic.max_dcdc = WM8350_DCDC_6; + wm8350->pmic.max_isink = WM8350_ISINK_B; + + switch (chip_rev) { + case 0: + dev_info(wm8350->dev, "WM8352 Rev A\n"); + wm8350->power.rev_g_coeff = 1; + break; + + default: + dev_err(wm8350->dev, "Unknown WM8352 CHIP_REV\n"); + ret = -ENODEV; + goto err; + } + break; + + default: + dev_err(wm8350->dev, "Unknown MASK_REV\n"); + ret = -ENODEV; + goto err; + } + + ret = wm8350_create_cache(wm8350, mask_rev, mode); + if (ret < 0) { + dev_err(wm8350->dev, "Failed to create register cache\n"); + return ret; + } + + mutex_init(&wm8350->auxadc_mutex); + + ret = wm8350_irq_init(wm8350, irq, pdata); + if (ret < 0) + goto err; + + if (pdata && pdata->init) { + ret = pdata->init(wm8350); + if (ret != 0) { + dev_err(wm8350->dev, "Platform init() failed: %d\n", + ret); + goto err_irq; + } + } + + wm8350_reg_write(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK, 0x0); + + wm8350_client_dev_register(wm8350, "wm8350-codec", + &(wm8350->codec.pdev)); + wm8350_client_dev_register(wm8350, "wm8350-gpio", + &(wm8350->gpio.pdev)); + wm8350_client_dev_register(wm8350, "wm8350-hwmon", + &(wm8350->hwmon.pdev)); + wm8350_client_dev_register(wm8350, "wm8350-power", + &(wm8350->power.pdev)); + wm8350_client_dev_register(wm8350, "wm8350-rtc", &(wm8350->rtc.pdev)); + wm8350_client_dev_register(wm8350, "wm8350-wdt", &(wm8350->wdt.pdev)); + + return 0; + +err_irq: + wm8350_irq_exit(wm8350); +err: + kfree(wm8350->reg_cache); + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_device_init); + +void wm8350_device_exit(struct wm8350 *wm8350) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wm8350->pmic.led); i++) + platform_device_unregister(wm8350->pmic.led[i].pdev); + + for (i = 0; i < ARRAY_SIZE(wm8350->pmic.pdev); i++) + platform_device_unregister(wm8350->pmic.pdev[i]); + + platform_device_unregister(wm8350->wdt.pdev); + platform_device_unregister(wm8350->rtc.pdev); + platform_device_unregister(wm8350->power.pdev); + platform_device_unregister(wm8350->hwmon.pdev); + platform_device_unregister(wm8350->gpio.pdev); + platform_device_unregister(wm8350->codec.pdev); + + wm8350_irq_exit(wm8350); + + kfree(wm8350->reg_cache); +} +EXPORT_SYMBOL_GPL(wm8350_device_exit); + +MODULE_DESCRIPTION("WM8350 AudioPlus PMIC core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/wm8350-gpio.c b/drivers/mfd/wm8350-gpio.c new file mode 100644 index 00000000000..ebf99bef392 --- /dev/null +++ b/drivers/mfd/wm8350-gpio.c @@ -0,0 +1,222 @@ +/* + * wm8350-core.c -- Device access for Wolfson WM8350 + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> + +#include <linux/mfd/wm8350/core.h> +#include <linux/mfd/wm8350/gpio.h> +#include <linux/mfd/wm8350/pmic.h> + +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; +} + +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_INT_MODE, 1 << gpio); + else + return wm8350_clear_bits(wm8350, + WM8350_GPIO_INT_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); diff --git a/drivers/mfd/wm8350-i2c.c b/drivers/mfd/wm8350-i2c.c new file mode 100644 index 00000000000..8d8c9321757 --- /dev/null +++ b/drivers/mfd/wm8350-i2c.c @@ -0,0 +1,131 @@ +/* + * wm8350-i2c.c -- Generic I2C driver for Wolfson WM8350 PMIC + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * 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. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/mfd/wm8350/core.h> + +static int wm8350_i2c_read_device(struct wm8350 *wm8350, char reg, + int bytes, void *dest) +{ + int ret; + + ret = i2c_master_send(wm8350->i2c_client, ®, 1); + if (ret < 0) + return ret; + ret = i2c_master_recv(wm8350->i2c_client, dest, bytes); + if (ret < 0) + return ret; + if (ret != bytes) + return -EIO; + return 0; +} + +static int wm8350_i2c_write_device(struct wm8350 *wm8350, char reg, + int bytes, void *src) +{ + /* we add 1 byte for device register */ + u8 msg[(WM8350_MAX_REGISTER << 1) + 1]; + int ret; + + if (bytes > ((WM8350_MAX_REGISTER << 1) + 1)) + return -EINVAL; + + msg[0] = reg; + memcpy(&msg[1], src, bytes); + ret = i2c_master_send(wm8350->i2c_client, msg, bytes + 1); + if (ret < 0) + return ret; + if (ret != bytes + 1) + return -EIO; + return 0; +} + +static int wm8350_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8350 *wm8350; + int ret = 0; + + wm8350 = kzalloc(sizeof(struct wm8350), GFP_KERNEL); + if (wm8350 == NULL) { + kfree(i2c); + return -ENOMEM; + } + + i2c_set_clientdata(i2c, wm8350); + wm8350->dev = &i2c->dev; + wm8350->i2c_client = i2c; + wm8350->read_dev = wm8350_i2c_read_device; + wm8350->write_dev = wm8350_i2c_write_device; + + ret = wm8350_device_init(wm8350, i2c->irq, i2c->dev.platform_data); + if (ret < 0) + goto err; + + return ret; + +err: + kfree(wm8350); + return ret; +} + +static int wm8350_i2c_remove(struct i2c_client *i2c) +{ + struct wm8350 *wm8350 = i2c_get_clientdata(i2c); + + wm8350_device_exit(wm8350); + kfree(wm8350); + + return 0; +} + +static const struct i2c_device_id wm8350_i2c_id[] = { + { "wm8350", 0 }, + { "wm8351", 0 }, + { "wm8352", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8350_i2c_id); + + +static struct i2c_driver wm8350_i2c_driver = { + .driver = { + .name = "wm8350", + .owner = THIS_MODULE, + }, + .probe = wm8350_i2c_probe, + .remove = wm8350_i2c_remove, + .id_table = wm8350_i2c_id, +}; + +static int __init wm8350_i2c_init(void) +{ + return i2c_add_driver(&wm8350_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(wm8350_i2c_init); + +static void __exit wm8350_i2c_exit(void) +{ + i2c_del_driver(&wm8350_i2c_driver); +} +module_exit(wm8350_i2c_exit); + +MODULE_DESCRIPTION("I2C support for the WM8350 AudioPlus PMIC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/wm8350-irq.c b/drivers/mfd/wm8350-irq.c new file mode 100644 index 00000000000..9025f29e270 --- /dev/null +++ b/drivers/mfd/wm8350-irq.c @@ -0,0 +1,529 @@ +/* + * wm8350-irq.c -- IRQ support for Wolfson WM8350 + * + * Copyright 2007, 2008, 2009 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood, Mark Brown + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/bug.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> + +#include <linux/mfd/wm8350/core.h> +#include <linux/mfd/wm8350/audio.h> +#include <linux/mfd/wm8350/comparator.h> +#include <linux/mfd/wm8350/gpio.h> +#include <linux/mfd/wm8350/pmic.h> +#include <linux/mfd/wm8350/rtc.h> +#include <linux/mfd/wm8350/supply.h> +#include <linux/mfd/wm8350/wdt.h> + +#define WM8350_NUM_IRQ_REGS 7 + +#define WM8350_INT_OFFSET_1 0 +#define WM8350_INT_OFFSET_2 1 +#define WM8350_POWER_UP_INT_OFFSET 2 +#define WM8350_UNDER_VOLTAGE_INT_OFFSET 3 +#define WM8350_OVER_CURRENT_INT_OFFSET 4 +#define WM8350_GPIO_INT_OFFSET 5 +#define WM8350_COMPARATOR_INT_OFFSET 6 + +struct wm8350_irq_data { + int primary; + int reg; + int mask; + int primary_only; +}; + +static struct wm8350_irq_data wm8350_irqs[] = { + [WM8350_IRQ_OC_LS] = { + .primary = WM8350_OC_INT, + .reg = WM8350_OVER_CURRENT_INT_OFFSET, + .mask = WM8350_OC_LS_EINT, + .primary_only = 1, + }, + [WM8350_IRQ_UV_DC1] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_DC1_EINT, + }, + [WM8350_IRQ_UV_DC2] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_DC2_EINT, + }, + [WM8350_IRQ_UV_DC3] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_DC3_EINT, + }, + [WM8350_IRQ_UV_DC4] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_DC4_EINT, + }, + [WM8350_IRQ_UV_DC5] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_DC5_EINT, + }, + [WM8350_IRQ_UV_DC6] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_DC6_EINT, + }, + [WM8350_IRQ_UV_LDO1] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_LDO1_EINT, + }, + [WM8350_IRQ_UV_LDO2] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_LDO2_EINT, + }, + [WM8350_IRQ_UV_LDO3] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_LDO3_EINT, + }, + [WM8350_IRQ_UV_LDO4] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_LDO4_EINT, + }, + [WM8350_IRQ_CHG_BAT_HOT] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_BAT_HOT_EINT, + }, + [WM8350_IRQ_CHG_BAT_COLD] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_BAT_COLD_EINT, + }, + [WM8350_IRQ_CHG_BAT_FAIL] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_BAT_FAIL_EINT, + }, + [WM8350_IRQ_CHG_TO] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_TO_EINT, + }, + [WM8350_IRQ_CHG_END] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_END_EINT, + }, + [WM8350_IRQ_CHG_START] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_START_EINT, + }, + [WM8350_IRQ_CHG_FAST_RDY] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_FAST_RDY_EINT, + }, + [WM8350_IRQ_CHG_VBATT_LT_3P9] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_VBATT_LT_3P9_EINT, + }, + [WM8350_IRQ_CHG_VBATT_LT_3P1] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_VBATT_LT_3P1_EINT, + }, + [WM8350_IRQ_CHG_VBATT_LT_2P85] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_VBATT_LT_2P85_EINT, + }, + [WM8350_IRQ_RTC_ALM] = { + .primary = WM8350_RTC_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_RTC_ALM_EINT, + }, + [WM8350_IRQ_RTC_SEC] = { + .primary = WM8350_RTC_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_RTC_SEC_EINT, + }, + [WM8350_IRQ_RTC_PER] = { + .primary = WM8350_RTC_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_RTC_PER_EINT, + }, + [WM8350_IRQ_CS1] = { + .primary = WM8350_CS_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_CS1_EINT, + }, + [WM8350_IRQ_CS2] = { + .primary = WM8350_CS_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_CS2_EINT, + }, + [WM8350_IRQ_SYS_HYST_COMP_FAIL] = { + .primary = WM8350_SYS_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_SYS_HYST_COMP_FAIL_EINT, + }, + [WM8350_IRQ_SYS_CHIP_GT115] = { + .primary = WM8350_SYS_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_SYS_CHIP_GT115_EINT, + }, + [WM8350_IRQ_SYS_CHIP_GT140] = { + .primary = WM8350_SYS_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_SYS_CHIP_GT140_EINT, + }, + [WM8350_IRQ_SYS_WDOG_TO] = { + .primary = WM8350_SYS_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_SYS_WDOG_TO_EINT, + }, + [WM8350_IRQ_AUXADC_DATARDY] = { + .primary = WM8350_AUXADC_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_AUXADC_DATARDY_EINT, + }, + [WM8350_IRQ_AUXADC_DCOMP4] = { + .primary = WM8350_AUXADC_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_AUXADC_DCOMP4_EINT, + }, + [WM8350_IRQ_AUXADC_DCOMP3] = { + .primary = WM8350_AUXADC_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_AUXADC_DCOMP3_EINT, + }, + [WM8350_IRQ_AUXADC_DCOMP2] = { + .primary = WM8350_AUXADC_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_AUXADC_DCOMP2_EINT, + }, + [WM8350_IRQ_AUXADC_DCOMP1] = { + .primary = WM8350_AUXADC_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_AUXADC_DCOMP1_EINT, + }, + [WM8350_IRQ_USB_LIMIT] = { + .primary = WM8350_USB_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_USB_LIMIT_EINT, + .primary_only = 1, + }, + [WM8350_IRQ_WKUP_OFF_STATE] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_OFF_STATE_EINT, + }, + [WM8350_IRQ_WKUP_HIB_STATE] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_HIB_STATE_EINT, + }, + [WM8350_IRQ_WKUP_CONV_FAULT] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_CONV_FAULT_EINT, + }, + [WM8350_IRQ_WKUP_WDOG_RST] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_WDOG_RST_EINT, + }, + [WM8350_IRQ_WKUP_GP_PWR_ON] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_GP_PWR_ON_EINT, + }, + [WM8350_IRQ_WKUP_ONKEY] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_ONKEY_EINT, + }, + [WM8350_IRQ_WKUP_GP_WAKEUP] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_GP_WAKEUP_EINT, + }, + [WM8350_IRQ_CODEC_JCK_DET_L] = { + .primary = WM8350_CODEC_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_CODEC_JCK_DET_L_EINT, + }, + [WM8350_IRQ_CODEC_JCK_DET_R] = { + .primary = WM8350_CODEC_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_CODEC_JCK_DET_R_EINT, + }, + [WM8350_IRQ_CODEC_MICSCD] = { + .primary = WM8350_CODEC_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_CODEC_MICSCD_EINT, + }, + [WM8350_IRQ_CODEC_MICD] = { + .primary = WM8350_CODEC_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_CODEC_MICD_EINT, + }, + [WM8350_IRQ_EXT_USB_FB] = { + .primary = WM8350_EXT_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_EXT_USB_FB_EINT, + }, + [WM8350_IRQ_EXT_WALL_FB] = { + .primary = WM8350_EXT_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_EXT_WALL_FB_EINT, + }, + [WM8350_IRQ_EXT_BAT_FB] = { + .primary = WM8350_EXT_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_EXT_BAT_FB_EINT, + }, + [WM8350_IRQ_GPIO(0)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP0_EINT, + }, + [WM8350_IRQ_GPIO(1)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP1_EINT, + }, + [WM8350_IRQ_GPIO(2)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP2_EINT, + }, + [WM8350_IRQ_GPIO(3)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP3_EINT, + }, + [WM8350_IRQ_GPIO(4)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP4_EINT, + }, + [WM8350_IRQ_GPIO(5)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP5_EINT, + }, + [WM8350_IRQ_GPIO(6)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP6_EINT, + }, + [WM8350_IRQ_GPIO(7)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP7_EINT, + }, + [WM8350_IRQ_GPIO(8)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP8_EINT, + }, + [WM8350_IRQ_GPIO(9)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP9_EINT, + }, + [WM8350_IRQ_GPIO(10)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP10_EINT, + }, + [WM8350_IRQ_GPIO(11)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP11_EINT, + }, + [WM8350_IRQ_GPIO(12)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP12_EINT, + }, +}; + +static void wm8350_irq_call_handler(struct wm8350 *wm8350, int irq) +{ + mutex_lock(&wm8350->irq_mutex); + + if (wm8350->irq[irq].handler) + wm8350->irq[irq].handler(irq, wm8350->irq[irq].data); + else { + dev_err(wm8350->dev, "irq %d nobody cared. now masked.\n", + irq); + wm8350_mask_irq(wm8350, irq); + } + + mutex_unlock(&wm8350->irq_mutex); +} + +/* + * This is a threaded IRQ handler so can access I2C/SPI. Since all + * interrupts are clear on read the IRQ line will be reasserted and + * the physical IRQ will be handled again if another interrupt is + * asserted while we run - in the normal course of events this is a + * rare occurrence so we save I2C/SPI reads. + */ +static irqreturn_t wm8350_irq(int irq, void *irq_data) +{ + struct wm8350 *wm8350 = irq_data; + u16 level_one; + u16 sub_reg[WM8350_NUM_IRQ_REGS]; + int read_done[WM8350_NUM_IRQ_REGS]; + struct wm8350_irq_data *data; + int i; + + /* TODO: Use block reads to improve performance? */ + level_one = wm8350_reg_read(wm8350, WM8350_SYSTEM_INTERRUPTS) + & ~wm8350_reg_read(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK); + + if (!level_one) + return IRQ_NONE; + + memset(&read_done, 0, sizeof(read_done)); + + for (i = 0; i < ARRAY_SIZE(wm8350_irqs); i++) { + data = &wm8350_irqs[i]; + + if (!(level_one & data->primary)) + continue; + + if (!read_done[data->reg]) { + sub_reg[data->reg] = + wm8350_reg_read(wm8350, WM8350_INT_STATUS_1 + + data->reg); + sub_reg[data->reg] &= + ~wm8350_reg_read(wm8350, + WM8350_INT_STATUS_1_MASK + + data->reg); + read_done[data->reg] = 1; + } + + if (sub_reg[data->reg] & data->mask) + wm8350_irq_call_handler(wm8350, i); + } + + return IRQ_HANDLED; +} + +int wm8350_register_irq(struct wm8350 *wm8350, int irq, + irq_handler_t handler, unsigned long flags, + const char *name, void *data) +{ + if (irq < 0 || irq >= WM8350_NUM_IRQ || !handler) + return -EINVAL; + + if (wm8350->irq[irq].handler) + return -EBUSY; + + mutex_lock(&wm8350->irq_mutex); + wm8350->irq[irq].handler = handler; + wm8350->irq[irq].data = data; + mutex_unlock(&wm8350->irq_mutex); + + wm8350_unmask_irq(wm8350, irq); + + 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; + + wm8350_mask_irq(wm8350, irq); + + mutex_lock(&wm8350->irq_mutex); + wm8350->irq[irq].handler = NULL; + mutex_unlock(&wm8350->irq_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_free_irq); + +int wm8350_mask_irq(struct wm8350 *wm8350, int irq) +{ + return wm8350_set_bits(wm8350, WM8350_INT_STATUS_1_MASK + + wm8350_irqs[irq].reg, + wm8350_irqs[irq].mask); +} +EXPORT_SYMBOL_GPL(wm8350_mask_irq); + +int wm8350_unmask_irq(struct wm8350 *wm8350, int irq) +{ + return wm8350_clear_bits(wm8350, WM8350_INT_STATUS_1_MASK + + wm8350_irqs[irq].reg, + wm8350_irqs[irq].mask); +} +EXPORT_SYMBOL_GPL(wm8350_unmask_irq); + +int wm8350_irq_init(struct wm8350 *wm8350, int irq, + struct wm8350_platform_data *pdata) +{ + int ret; + int flags = IRQF_ONESHOT; + + if (!irq) { + dev_err(wm8350->dev, "No IRQ configured\n"); + return -EINVAL; + } + + wm8350_reg_write(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK, 0xFFFF); + wm8350_reg_write(wm8350, WM8350_INT_STATUS_1_MASK, 0xFFFF); + wm8350_reg_write(wm8350, WM8350_INT_STATUS_2_MASK, 0xFFFF); + wm8350_reg_write(wm8350, WM8350_UNDER_VOLTAGE_INT_STATUS_MASK, 0xFFFF); + wm8350_reg_write(wm8350, WM8350_GPIO_INT_STATUS_MASK, 0xFFFF); + wm8350_reg_write(wm8350, WM8350_COMPARATOR_INT_STATUS_MASK, 0xFFFF); + + mutex_init(&wm8350->irq_mutex); + wm8350->chip_irq = irq; + + if (pdata && pdata->irq_high) { + flags |= IRQF_TRIGGER_HIGH; + + wm8350_set_bits(wm8350, WM8350_SYSTEM_CONTROL_1, + WM8350_IRQ_POL); + } else { + flags |= IRQF_TRIGGER_LOW; + + wm8350_clear_bits(wm8350, WM8350_SYSTEM_CONTROL_1, + WM8350_IRQ_POL); + } + + ret = request_threaded_irq(irq, NULL, wm8350_irq, flags, + "wm8350", wm8350); + if (ret != 0) + dev_err(wm8350->dev, "Failed to request IRQ: %d\n", ret); + + return ret; +} + +int wm8350_irq_exit(struct wm8350 *wm8350) +{ + free_irq(wm8350->chip_irq, wm8350); + return 0; +} diff --git a/drivers/mfd/wm8350-regmap.c b/drivers/mfd/wm8350-regmap.c new file mode 100644 index 00000000000..e965139e5cd --- /dev/null +++ b/drivers/mfd/wm8350-regmap.c @@ -0,0 +1,3435 @@ +/* + * wm8350-regmap.c -- Wolfson Microelectronics WM8350 register map + * + * This file splits out the tables describing the defaults and access + * status of the WM8350 registers since they are rather large. + * + * Copyright 2007, 2008 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 as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/mfd/wm8350/core.h> + +#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_0 + +#undef WM8350_HAVE_CONFIG_MODE +#define WM8350_HAVE_CONFIG_MODE + +const u16 wm8350_mode0_defaults[] = { + 0x17FF, /* R0 - Reset/ID */ + 0x1000, /* R1 - ID */ + 0x0000, /* R2 */ + 0x1002, /* R3 - System Control 1 */ + 0x0004, /* R4 - System Control 2 */ + 0x0000, /* R5 - System Hibernate */ + 0x8A00, /* R6 - Interface Control */ + 0x0000, /* R7 */ + 0x8000, /* R8 - Power mgmt (1) */ + 0x0000, /* R9 - Power mgmt (2) */ + 0x0000, /* R10 - Power mgmt (3) */ + 0x2000, /* R11 - Power mgmt (4) */ + 0x0E00, /* R12 - Power mgmt (5) */ + 0x0000, /* R13 - Power mgmt (6) */ + 0x0000, /* R14 - Power mgmt (7) */ + 0x0000, /* R15 */ + 0x0000, /* R16 - RTC Seconds/Minutes */ + 0x0100, /* R17 - RTC Hours/Day */ + 0x0101, /* R18 - RTC Date/Month */ + 0x1400, /* R19 - RTC Year */ + 0x0000, /* R20 - Alarm Seconds/Minutes */ + 0x0000, /* R21 - Alarm Hours/Day */ + 0x0000, /* R22 - Alarm Date/Month */ + 0x0320, /* R23 - RTC Time Control */ + 0x0000, /* R24 - System Interrupts */ + 0x0000, /* R25 - Interrupt Status 1 */ + 0x0000, /* R26 - Interrupt Status 2 */ + 0x0000, /* R27 - Power Up Interrupt Status */ + 0x0000, /* R28 - Under Voltage Interrupt status */ + 0x0000, /* R29 - Over Current Interrupt status */ + 0x0000, /* R30 - GPIO Interrupt Status */ + 0x0000, /* R31 - Comparator Interrupt Status */ + 0x3FFF, /* R32 - System Interrupts Mask */ + 0x0000, /* R33 - Interrupt Status 1 Mask */ + 0x0000, /* R34 - Interrupt Status 2 Mask */ + 0x0000, /* R35 - Power Up Interrupt Status Mask */ + 0x0000, /* R36 - Under Voltage Interrupt status Mask */ + 0x0000, /* R37 - Over Current Interrupt status Mask */ + 0x0000, /* R38 - GPIO Interrupt Status Mask */ + 0x0000, /* R39 - Comparator Interrupt Status Mask */ + 0x0040, /* R40 - Clock Control 1 */ + 0x0000, /* R41 - Clock Control 2 */ + 0x3B00, /* R42 - FLL Control 1 */ + 0x7086, /* R43 - FLL Control 2 */ + 0xC226, /* R44 - FLL Control 3 */ + 0x0000, /* R45 - FLL Control 4 */ + 0x0000, /* R46 */ + 0x0000, /* R47 */ + 0x0000, /* R48 - DAC Control */ + 0x0000, /* R49 */ + 0x00C0, /* R50 - DAC Digital Volume L */ + 0x00C0, /* R51 - DAC Digital Volume R */ + 0x0000, /* R52 */ + 0x0040, /* R53 - DAC LR Rate */ + 0x0000, /* R54 - DAC Clock Control */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x4000, /* R58 - DAC Mute */ + 0x0000, /* R59 - DAC Mute Volume */ + 0x0000, /* R60 - DAC Side */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x8000, /* R64 - ADC Control */ + 0x0000, /* R65 */ + 0x00C0, /* R66 - ADC Digital Volume L */ + 0x00C0, /* R67 - ADC Digital Volume R */ + 0x0000, /* R68 - ADC Divider */ + 0x0000, /* R69 */ + 0x0040, /* R70 - ADC LR Rate */ + 0x0000, /* R71 */ + 0x0303, /* R72 - Input Control */ + 0x0000, /* R73 - IN3 Input Control */ + 0x0000, /* R74 - Mic Bias Control */ + 0x0000, /* R75 */ + 0x0000, /* R76 - Output Control */ + 0x0000, /* R77 - Jack Detect */ + 0x0000, /* R78 - Anti Pop Control */ + 0x0000, /* R79 */ + 0x0040, /* R80 - Left Input Volume */ + 0x0040, /* R81 - Right Input Volume */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 */ + 0x0800, /* R88 - Left Mixer Control */ + 0x1000, /* R89 - Right Mixer Control */ + 0x0000, /* R90 */ + 0x0000, /* R91 */ + 0x0000, /* R92 - OUT3 Mixer Control */ + 0x0000, /* R93 - OUT4 Mixer Control */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 - Output Left Mixer Volume */ + 0x0000, /* R97 - Output Right Mixer Volume */ + 0x0000, /* R98 - Input Mixer Volume L */ + 0x0000, /* R99 - Input Mixer Volume R */ + 0x0000, /* R100 - Input Mixer Volume */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x00E4, /* R104 - LOUT1 Volume */ + 0x00E4, /* R105 - ROUT1 Volume */ + 0x00E4, /* R106 - LOUT2 Volume */ + 0x02E4, /* R107 - ROUT2 Volume */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 - BEEP Volume */ + 0x0A00, /* R112 - AI Formating */ + 0x0000, /* R113 - ADC DAC COMP */ + 0x0020, /* R114 - AI ADC Control */ + 0x0020, /* R115 - AI DAC Control */ + 0x0000, /* R116 - AIF Test */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x1FFF, /* R128 - GPIO Debounce */ + 0x0000, /* R129 - GPIO Pin pull up Control */ + 0x03FC, /* R130 - GPIO Pull down Control */ + 0x0000, /* R131 - GPIO Interrupt Mode */ + 0x0000, /* R132 */ + 0x0000, /* R133 - GPIO Control */ + 0x0FFC, /* R134 - GPIO Configuration (i/o) */ + 0x0FFC, /* R135 - GPIO Pin Polarity / Type */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0013, /* R140 - GPIO Function Select 1 */ + 0x0000, /* R141 - GPIO Function Select 2 */ + 0x0000, /* R142 - GPIO Function Select 3 */ + 0x0003, /* R143 - GPIO Function Select 4 */ + 0x0000, /* R144 - Digitiser Control (1) */ + 0x0002, /* R145 - Digitiser Control (2) */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x7000, /* R152 - AUX1 Readback */ + 0x7000, /* R153 - AUX2 Readback */ + 0x7000, /* R154 - AUX3 Readback */ + 0x7000, /* R155 - AUX4 Readback */ + 0x0000, /* R156 - USB Voltage Readback */ + 0x0000, /* R157 - LINE Voltage Readback */ + 0x0000, /* R158 - BATT Voltage Readback */ + 0x0000, /* R159 - Chip Temp Readback */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 - Generic Comparator Control */ + 0x0000, /* R164 - Generic comparator 1 */ + 0x0000, /* R165 - Generic comparator 2 */ + 0x0000, /* R166 - Generic comparator 3 */ + 0x0000, /* R167 - Generic comparator 4 */ + 0xA00F, /* R168 - Battery Charger Control 1 */ + 0x0B06, /* R169 - Battery Charger Control 2 */ + 0x0000, /* R170 - Battery Charger Control 3 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Current Sink Driver A */ + 0x0000, /* R173 - CSA Flash control */ + 0x0000, /* R174 - Current Sink Driver B */ + 0x0000, /* R175 - CSB Flash control */ + 0x0000, /* R176 - DCDC/LDO requested */ + 0x002D, /* R177 - DCDC Active options */ + 0x0000, /* R178 - DCDC Sleep options */ + 0x0025, /* R179 - Power-check comparator */ + 0x000E, /* R180 - DCDC1 Control */ + 0x0000, /* R181 - DCDC1 Timeouts */ + 0x1006, /* R182 - DCDC1 Low Power */ + 0x0018, /* R183 - DCDC2 Control */ + 0x0000, /* R184 - DCDC2 Timeouts */ + 0x0000, /* R185 */ + 0x0000, /* R186 - DCDC3 Control */ + 0x0000, /* R187 - DCDC3 Timeouts */ + 0x0006, /* R188 - DCDC3 Low Power */ + 0x0000, /* R189 - DCDC4 Control */ + 0x0000, /* R190 - DCDC4 Timeouts */ + 0x0006, /* R191 - DCDC4 Low Power */ + 0x0008, /* R192 - DCDC5 Control */ + 0x0000, /* R193 - DCDC5 Timeouts */ + 0x0000, /* R194 */ + 0x0000, /* R195 - DCDC6 Control */ + 0x0000, /* R196 - DCDC6 Timeouts */ + 0x0006, /* R197 - DCDC6 Low Power */ + 0x0000, /* R198 */ + 0x0003, /* R199 - Limit Switch Control */ + 0x001C, /* R200 - LDO1 Control */ + 0x0000, /* R201 - LDO1 Timeouts */ + 0x001C, /* R202 - LDO1 Low Power */ + 0x001B, /* R203 - LDO2 Control */ + 0x0000, /* R204 - LDO2 Timeouts */ + 0x001C, /* R205 - LDO2 Low Power */ + 0x001B, /* R206 - LDO3 Control */ + 0x0000, /* R207 - LDO3 Timeouts */ + 0x001C, /* R208 - LDO3 Low Power */ + 0x001B, /* R209 - LDO4 Control */ + 0x0000, /* R210 - LDO4 Timeouts */ + 0x001C, /* R211 - LDO4 Low Power */ + 0x0000, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 - VCC_FAULT Masks */ + 0x001F, /* R216 - Main Bandgap Control */ + 0x0000, /* R217 - OSC Control */ + 0x9000, /* R218 - RTC Tick Control */ + 0x0000, /* R219 */ + 0x4000, /* R220 - RAM BIST 1 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0000, /* R223 */ + 0x0000, /* R224 */ + 0x0000, /* R225 - DCDC/LDO status */ + 0x0000, /* R226 */ + 0x0000, /* R227 */ + 0x0000, /* R228 */ + 0x0000, /* R229 */ + 0xE000, /* R230 - GPIO Pin Status */ + 0x0000, /* R231 */ + 0x0000, /* R232 */ + 0x0000, /* R233 */ + 0x0000, /* R234 */ + 0x0000, /* R235 */ + 0x0000, /* R236 */ + 0x0000, /* R237 */ + 0x0000, /* R238 */ + 0x0000, /* R239 */ + 0x0000, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0000, /* R243 */ + 0x0000, /* R244 */ + 0x0000, /* R245 */ + 0x0000, /* R246 */ + 0x0000, /* R247 */ + 0x0000, /* R248 */ + 0x0000, /* R249 */ + 0x0000, /* R250 */ + 0x0000, /* R251 */ + 0x0000, /* R252 */ + 0x0000, /* R253 */ + 0x0000, /* R254 */ + 0x0000, /* R255 */ +}; +#endif + +#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_1 + +#undef WM8350_HAVE_CONFIG_MODE +#define WM8350_HAVE_CONFIG_MODE + +const u16 wm8350_mode1_defaults[] = { + 0x17FF, /* R0 - Reset/ID */ + 0x1000, /* R1 - ID */ + 0x0000, /* R2 */ + 0x1002, /* R3 - System Control 1 */ + 0x0014, /* R4 - System Control 2 */ + 0x0000, /* R5 - System Hibernate */ + 0x8A00, /* R6 - Interface Control */ + 0x0000, /* R7 */ + 0x8000, /* R8 - Power mgmt (1) */ + 0x0000, /* R9 - Power mgmt (2) */ + 0x0000, /* R10 - Power mgmt (3) */ + 0x2000, /* R11 - Power mgmt (4) */ + 0x0E00, /* R12 - Power mgmt (5) */ + 0x0000, /* R13 - Power mgmt (6) */ + 0x0000, /* R14 - Power mgmt (7) */ + 0x0000, /* R15 */ + 0x0000, /* R16 - RTC Seconds/Minutes */ + 0x0100, /* R17 - RTC Hours/Day */ + 0x0101, /* R18 - RTC Date/Month */ + 0x1400, /* R19 - RTC Year */ + 0x0000, /* R20 - Alarm Seconds/Minutes */ + 0x0000, /* R21 - Alarm Hours/Day */ + 0x0000, /* R22 - Alarm Date/Month */ + 0x0320, /* R23 - RTC Time Control */ + 0x0000, /* R24 - System Interrupts */ + 0x0000, /* R25 - Interrupt Status 1 */ + 0x0000, /* R26 - Interrupt Status 2 */ + 0x0000, /* R27 - Power Up Interrupt Status */ + 0x0000, /* R28 - Under Voltage Interrupt status */ + 0x0000, /* R29 - Over Current Interrupt status */ + 0x0000, /* R30 - GPIO Interrupt Status */ + 0x0000, /* R31 - Comparator Interrupt Status */ + 0x3FFF, /* R32 - System Interrupts Mask */ + 0x0000, /* R33 - Interrupt Status 1 Mask */ + 0x0000, /* R34 - Interrupt Status 2 Mask */ + 0x0000, /* R35 - Power Up Interrupt Status Mask */ + 0x0000, /* R36 - Under Voltage Interrupt status Mask */ + 0x0000, /* R37 - Over Current Interrupt status Mask */ + 0x0000, /* R38 - GPIO Interrupt Status Mask */ + 0x0000, /* R39 - Comparator Interrupt Status Mask */ + 0x0040, /* R40 - Clock Control 1 */ + 0x0000, /* R41 - Clock Control 2 */ + 0x3B00, /* R42 - FLL Control 1 */ + 0x7086, /* R43 - FLL Control 2 */ + 0xC226, /* R44 - FLL Control 3 */ + 0x0000, /* R45 - FLL Control 4 */ + 0x0000, /* R46 */ + 0x0000, /* R47 */ + 0x0000, /* R48 - DAC Control */ + 0x0000, /* R49 */ + 0x00C0, /* R50 - DAC Digital Volume L */ + 0x00C0, /* R51 - DAC Digital Volume R */ + 0x0000, /* R52 */ + 0x0040, /* R53 - DAC LR Rate */ + 0x0000, /* R54 - DAC Clock Control */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x4000, /* R58 - DAC Mute */ + 0x0000, /* R59 - DAC Mute Volume */ + 0x0000, /* R60 - DAC Side */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x8000, /* R64 - ADC Control */ + 0x0000, /* R65 */ + 0x00C0, /* R66 - ADC Digital Volume L */ + 0x00C0, /* R67 - ADC Digital Volume R */ + 0x0000, /* R68 - ADC Divider */ + 0x0000, /* R69 */ + 0x0040, /* R70 - ADC LR Rate */ + 0x0000, /* R71 */ + 0x0303, /* R72 - Input Control */ + 0x0000, /* R73 - IN3 Input Control */ + 0x0000, /* R74 - Mic Bias Control */ + 0x0000, /* R75 */ + 0x0000, /* R76 - Output Control */ + 0x0000, /* R77 - Jack Detect */ + 0x0000, /* R78 - Anti Pop Control */ + 0x0000, /* R79 */ + 0x0040, /* R80 - Left Input Volume */ + 0x0040, /* R81 - Right Input Volume */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 */ + 0x0800, /* R88 - Left Mixer Control */ + 0x1000, /* R89 - Right Mixer Control */ + 0x0000, /* R90 */ + 0x0000, /* R91 */ + 0x0000, /* R92 - OUT3 Mixer Control */ + 0x0000, /* R93 - OUT4 Mixer Control */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 - Output Left Mixer Volume */ + 0x0000, /* R97 - Output Right Mixer Volume */ + 0x0000, /* R98 - Input Mixer Volume L */ + 0x0000, /* R99 - Input Mixer Volume R */ + 0x0000, /* R100 - Input Mixer Volume */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x00E4, /* R104 - LOUT1 Volume */ + 0x00E4, /* R105 - ROUT1 Volume */ + 0x00E4, /* R106 - LOUT2 Volume */ + 0x02E4, /* R107 - ROUT2 Volume */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 - BEEP Volume */ + 0x0A00, /* R112 - AI Formating */ + 0x0000, /* R113 - ADC DAC COMP */ + 0x0020, /* R114 - AI ADC Control */ + 0x0020, /* R115 - AI DAC Control */ + 0x0000, /* R116 - AIF Test */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x1FFF, /* R128 - GPIO Debounce */ + 0x0000, /* R129 - GPIO Pin pull up Control */ + 0x03FC, /* R130 - GPIO Pull down Control */ + 0x0000, /* R131 - GPIO Interrupt Mode */ + 0x0000, /* R132 */ + 0x0000, /* R133 - GPIO Control */ + 0x00FB, /* R134 - GPIO Configuration (i/o) */ + 0x04FE, /* R135 - GPIO Pin Polarity / Type */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0312, /* R140 - GPIO Function Select 1 */ + 0x1003, /* R141 - GPIO Function Select 2 */ + 0x1331, /* R142 - GPIO Function Select 3 */ + 0x0003, /* R143 - GPIO Function Select 4 */ + 0x0000, /* R144 - Digitiser Control (1) */ + 0x0002, /* R145 - Digitiser Control (2) */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x7000, /* R152 - AUX1 Readback */ + 0x7000, /* R153 - AUX2 Readback */ + 0x7000, /* R154 - AUX3 Readback */ + 0x7000, /* R155 - AUX4 Readback */ + 0x0000, /* R156 - USB Voltage Readback */ + 0x0000, /* R157 - LINE Voltage Readback */ + 0x0000, /* R158 - BATT Voltage Readback */ + 0x0000, /* R159 - Chip Temp Readback */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 - Generic Comparator Control */ + 0x0000, /* R164 - Generic comparator 1 */ + 0x0000, /* R165 - Generic comparator 2 */ + 0x0000, /* R166 - Generic comparator 3 */ + 0x0000, /* R167 - Generic comparator 4 */ + 0xA00F, /* R168 - Battery Charger Control 1 */ + 0x0B06, /* R169 - Battery Charger Control 2 */ + 0x0000, /* R170 - Battery Charger Control 3 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Current Sink Driver A */ + 0x0000, /* R173 - CSA Flash control */ + 0x0000, /* R174 - Current Sink Driver B */ + 0x0000, /* R175 - CSB Flash control */ + 0x0000, /* R176 - DCDC/LDO requested */ + 0x002D, /* R177 - DCDC Active options */ + 0x0000, /* R178 - DCDC Sleep options */ + 0x0025, /* R179 - Power-check comparator */ + 0x0062, /* R180 - DCDC1 Control */ + 0x0400, /* R181 - DCDC1 Timeouts */ + 0x1006, /* R182 - DCDC1 Low Power */ + 0x0018, /* R183 - DCDC2 Control */ + 0x0000, /* R184 - DCDC2 Timeouts */ + 0x0000, /* R185 */ + 0x0026, /* R186 - DCDC3 Control */ + 0x0400, /* R187 - DCDC3 Timeouts */ + 0x0006, /* R188 - DCDC3 Low Power */ + 0x0062, /* R189 - DCDC4 Control */ + 0x0400, /* R190 - DCDC4 Timeouts */ + 0x0006, /* R191 - DCDC4 Low Power */ + 0x0008, /* R192 - DCDC5 Control */ + 0x0000, /* R193 - DCDC5 Timeouts */ + 0x0000, /* R194 */ + 0x0026, /* R195 - DCDC6 Control */ + 0x0800, /* R196 - DCDC6 Timeouts */ + 0x0006, /* R197 - DCDC6 Low Power */ + 0x0000, /* R198 */ + 0x0003, /* R199 - Limit Switch Control */ + 0x0006, /* R200 - LDO1 Control */ + 0x0400, /* R201 - LDO1 Timeouts */ + 0x001C, /* R202 - LDO1 Low Power */ + 0x0006, /* R203 - LDO2 Control */ + 0x0400, /* R204 - LDO2 Timeouts */ + 0x001C, /* R205 - LDO2 Low Power */ + 0x001B, /* R206 - LDO3 Control */ + 0x0000, /* R207 - LDO3 Timeouts */ + 0x001C, /* R208 - LDO3 Low Power */ + 0x001B, /* R209 - LDO4 Control */ + 0x0000, /* R210 - LDO4 Timeouts */ + 0x001C, /* R211 - LDO4 Low Power */ + 0x0000, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 - VCC_FAULT Masks */ + 0x001F, /* R216 - Main Bandgap Control */ + 0x0000, /* R217 - OSC Control */ + 0x9000, /* R218 - RTC Tick Control */ + 0x0000, /* R219 */ + 0x4000, /* R220 - RAM BIST 1 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0000, /* R223 */ + 0x0000, /* R224 */ + 0x0000, /* R225 - DCDC/LDO status */ + 0x0000, /* R226 */ + 0x0000, /* R227 */ + 0x0000, /* R228 */ + 0x0000, /* R229 */ + 0xE000, /* R230 - GPIO Pin Status */ + 0x0000, /* R231 */ + 0x0000, /* R232 */ + 0x0000, /* R233 */ + 0x0000, /* R234 */ + 0x0000, /* R235 */ + 0x0000, /* R236 */ + 0x0000, /* R237 */ + 0x0000, /* R238 */ + 0x0000, /* R239 */ + 0x0000, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0000, /* R243 */ + 0x0000, /* R244 */ + 0x0000, /* R245 */ + 0x0000, /* R246 */ + 0x0000, /* R247 */ + 0x0000, /* R248 */ + 0x0000, /* R249 */ + 0x0000, /* R250 */ + 0x0000, /* R251 */ + 0x0000, /* R252 */ + 0x0000, /* R253 */ + 0x0000, /* R254 */ + 0x0000, /* R255 */ +}; +#endif + +#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_2 + +#undef WM8350_HAVE_CONFIG_MODE +#define WM8350_HAVE_CONFIG_MODE + +const u16 wm8350_mode2_defaults[] = { + 0x17FF, /* R0 - Reset/ID */ + 0x1000, /* R1 - ID */ + 0x0000, /* R2 */ + 0x1002, /* R3 - System Control 1 */ + 0x0014, /* R4 - System Control 2 */ + 0x0000, /* R5 - System Hibernate */ + 0x8A00, /* R6 - Interface Control */ + 0x0000, /* R7 */ + 0x8000, /* R8 - Power mgmt (1) */ + 0x0000, /* R9 - Power mgmt (2) */ + 0x0000, /* R10 - Power mgmt (3) */ + 0x2000, /* R11 - Power mgmt (4) */ + 0x0E00, /* R12 - Power mgmt (5) */ + 0x0000, /* R13 - Power mgmt (6) */ + 0x0000, /* R14 - Power mgmt (7) */ + 0x0000, /* R15 */ + 0x0000, /* R16 - RTC Seconds/Minutes */ + 0x0100, /* R17 - RTC Hours/Day */ + 0x0101, /* R18 - RTC Date/Month */ + 0x1400, /* R19 - RTC Year */ + 0x0000, /* R20 - Alarm Seconds/Minutes */ + 0x0000, /* R21 - Alarm Hours/Day */ + 0x0000, /* R22 - Alarm Date/Month */ + 0x0320, /* R23 - RTC Time Control */ + 0x0000, /* R24 - System Interrupts */ + 0x0000, /* R25 - Interrupt Status 1 */ + 0x0000, /* R26 - Interrupt Status 2 */ + 0x0000, /* R27 - Power Up Interrupt Status */ + 0x0000, /* R28 - Under Voltage Interrupt status */ + 0x0000, /* R29 - Over Current Interrupt status */ + 0x0000, /* R30 - GPIO Interrupt Status */ + 0x0000, /* R31 - Comparator Interrupt Status */ + 0x3FFF, /* R32 - System Interrupts Mask */ + 0x0000, /* R33 - Interrupt Status 1 Mask */ + 0x0000, /* R34 - Interrupt Status 2 Mask */ + 0x0000, /* R35 - Power Up Interrupt Status Mask */ + 0x0000, /* R36 - Under Voltage Interrupt status Mask */ + 0x0000, /* R37 - Over Current Interrupt status Mask */ + 0x0000, /* R38 - GPIO Interrupt Status Mask */ + 0x0000, /* R39 - Comparator Interrupt Status Mask */ + 0x0040, /* R40 - Clock Control 1 */ + 0x0000, /* R41 - Clock Control 2 */ + 0x3B00, /* R42 - FLL Control 1 */ + 0x7086, /* R43 - FLL Control 2 */ + 0xC226, /* R44 - FLL Control 3 */ + 0x0000, /* R45 - FLL Control 4 */ + 0x0000, /* R46 */ + 0x0000, /* R47 */ + 0x0000, /* R48 - DAC Control */ + 0x0000, /* R49 */ + 0x00C0, /* R50 - DAC Digital Volume L */ + 0x00C0, /* R51 - DAC Digital Volume R */ + 0x0000, /* R52 */ + 0x0040, /* R53 - DAC LR Rate */ + 0x0000, /* R54 - DAC Clock Control */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x4000, /* R58 - DAC Mute */ + 0x0000, /* R59 - DAC Mute Volume */ + 0x0000, /* R60 - DAC Side */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x8000, /* R64 - ADC Control */ + 0x0000, /* R65 */ + 0x00C0, /* R66 - ADC Digital Volume L */ + 0x00C0, /* R67 - ADC Digital Volume R */ + 0x0000, /* R68 - ADC Divider */ + 0x0000, /* R69 */ + 0x0040, /* R70 - ADC LR Rate */ + 0x0000, /* R71 */ + 0x0303, /* R72 - Input Control */ + 0x0000, /* R73 - IN3 Input Control */ + 0x0000, /* R74 - Mic Bias Control */ + 0x0000, /* R75 */ + 0x0000, /* R76 - Output Control */ + 0x0000, /* R77 - Jack Detect */ + 0x0000, /* R78 - Anti Pop Control */ + 0x0000, /* R79 */ + 0x0040, /* R80 - Left Input Volume */ + 0x0040, /* R81 - Right Input Volume */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 */ + 0x0800, /* R88 - Left Mixer Control */ + 0x1000, /* R89 - Right Mixer Control */ + 0x0000, /* R90 */ + 0x0000, /* R91 */ + 0x0000, /* R92 - OUT3 Mixer Control */ + 0x0000, /* R93 - OUT4 Mixer Control */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 - Output Left Mixer Volume */ + 0x0000, /* R97 - Output Right Mixer Volume */ + 0x0000, /* R98 - Input Mixer Volume L */ + 0x0000, /* R99 - Input Mixer Volume R */ + 0x0000, /* R100 - Input Mixer Volume */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x00E4, /* R104 - LOUT1 Volume */ + 0x00E4, /* R105 - ROUT1 Volume */ + 0x00E4, /* R106 - LOUT2 Volume */ + 0x02E4, /* R107 - ROUT2 Volume */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 - BEEP Volume */ + 0x0A00, /* R112 - AI Formating */ + 0x0000, /* R113 - ADC DAC COMP */ + 0x0020, /* R114 - AI ADC Control */ + 0x0020, /* R115 - AI DAC Control */ + 0x0000, /* R116 - AIF Test */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x1FFF, /* R128 - GPIO Debounce */ + 0x0000, /* R129 - GPIO Pin pull up Control */ + 0x03FC, /* R130 - GPIO Pull down Control */ + 0x0000, /* R131 - GPIO Interrupt Mode */ + 0x0000, /* R132 */ + 0x0000, /* R133 - GPIO Control */ + 0x08FB, /* R134 - GPIO Configuration (i/o) */ + 0x0CFE, /* R135 - GPIO Pin Polarity / Type */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0312, /* R140 - GPIO Function Select 1 */ + 0x0003, /* R141 - GPIO Function Select 2 */ + 0x2331, /* R142 - GPIO Function Select 3 */ + 0x0003, /* R143 - GPIO Function Select 4 */ + 0x0000, /* R144 - Digitiser Control (1) */ + 0x0002, /* R145 - Digitiser Control (2) */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x7000, /* R152 - AUX1 Readback */ + 0x7000, /* R153 - AUX2 Readback */ + 0x7000, /* R154 - AUX3 Readback */ + 0x7000, /* R155 - AUX4 Readback */ + 0x0000, /* R156 - USB Voltage Readback */ + 0x0000, /* R157 - LINE Voltage Readback */ + 0x0000, /* R158 - BATT Voltage Readback */ + 0x0000, /* R159 - Chip Temp Readback */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 - Generic Comparator Control */ + 0x0000, /* R164 - Generic comparator 1 */ + 0x0000, /* R165 - Generic comparator 2 */ + 0x0000, /* R166 - Generic comparator 3 */ + 0x0000, /* R167 - Generic comparator 4 */ + 0xA00F, /* R168 - Battery Charger Control 1 */ + 0x0B06, /* R169 - Battery Charger Control 2 */ + 0x0000, /* R170 - Battery Charger Control 3 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Current Sink Driver A */ + 0x0000, /* R173 - CSA Flash control */ + 0x0000, /* R174 - Current Sink Driver B */ + 0x0000, /* R175 - CSB Flash control */ + 0x0000, /* R176 - DCDC/LDO requested */ + 0x002D, /* R177 - DCDC Active options */ + 0x0000, /* R178 - DCDC Sleep options */ + 0x0025, /* R179 - Power-check comparator */ + 0x000E, /* R180 - DCDC1 Control */ + 0x0400, /* R181 - DCDC1 Timeouts */ + 0x1006, /* R182 - DCDC1 Low Power */ + 0x0018, /* R183 - DCDC2 Control */ + 0x0000, /* R184 - DCDC2 Timeouts */ + 0x0000, /* R185 */ + 0x002E, /* R186 - DCDC3 Control */ + 0x0800, /* R187 - DCDC3 Timeouts */ + 0x0006, /* R188 - DCDC3 Low Power */ + 0x000E, /* R189 - DCDC4 Control */ + 0x0800, /* R190 - DCDC4 Timeouts */ + 0x0006, /* R191 - DCDC4 Low Power */ + 0x0008, /* R192 - DCDC5 Control */ + 0x0000, /* R193 - DCDC5 Timeouts */ + 0x0000, /* R194 */ + 0x0026, /* R195 - DCDC6 Control */ + 0x0C00, /* R196 - DCDC6 Timeouts */ + 0x0006, /* R197 - DCDC6 Low Power */ + 0x0000, /* R198 */ + 0x0003, /* R199 - Limit Switch Control */ + 0x001A, /* R200 - LDO1 Control */ + 0x0800, /* R201 - LDO1 Timeouts */ + 0x001C, /* R202 - LDO1 Low Power */ + 0x0010, /* R203 - LDO2 Control */ + 0x0800, /* R204 - LDO2 Timeouts */ + 0x001C, /* R205 - LDO2 Low Power */ + 0x000A, /* R206 - LDO3 Control */ + 0x0C00, /* R207 - LDO3 Timeouts */ + 0x001C, /* R208 - LDO3 Low Power */ + 0x001A, /* R209 - LDO4 Control */ + 0x0800, /* R210 - LDO4 Timeouts */ + 0x001C, /* R211 - LDO4 Low Power */ + 0x0000, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 - VCC_FAULT Masks */ + 0x001F, /* R216 - Main Bandgap Control */ + 0x0000, /* R217 - OSC Control */ + 0x9000, /* R218 - RTC Tick Control */ + 0x0000, /* R219 */ + 0x4000, /* R220 - RAM BIST 1 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0000, /* R223 */ + 0x0000, /* R224 */ + 0x0000, /* R225 - DCDC/LDO status */ + 0x0000, /* R226 */ + 0x0000, /* R227 */ + 0x0000, /* R228 */ + 0x0000, /* R229 */ + 0xE000, /* R230 - GPIO Pin Status */ + 0x0000, /* R231 */ + 0x0000, /* R232 */ + 0x0000, /* R233 */ + 0x0000, /* R234 */ + 0x0000, /* R235 */ + 0x0000, /* R236 */ + 0x0000, /* R237 */ + 0x0000, /* R238 */ + 0x0000, /* R239 */ + 0x0000, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0000, /* R243 */ + 0x0000, /* R244 */ + 0x0000, /* R245 */ + 0x0000, /* R246 */ + 0x0000, /* R247 */ + 0x0000, /* R248 */ + 0x0000, /* R249 */ + 0x0000, /* R250 */ + 0x0000, /* R251 */ + 0x0000, /* R252 */ + 0x0000, /* R253 */ + 0x0000, /* R254 */ + 0x0000, /* R255 */ +}; +#endif + +#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_3 + +#undef WM8350_HAVE_CONFIG_MODE +#define WM8350_HAVE_CONFIG_MODE + +const u16 wm8350_mode3_defaults[] = { + 0x17FF, /* R0 - Reset/ID */ + 0x1000, /* R1 - ID */ + 0x0000, /* R2 */ + 0x1000, /* R3 - System Control 1 */ + 0x0004, /* R4 - System Control 2 */ + 0x0000, /* R5 - System Hibernate */ + 0x8A00, /* R6 - Interface Control */ + 0x0000, /* R7 */ + 0x8000, /* R8 - Power mgmt (1) */ + 0x0000, /* R9 - Power mgmt (2) */ + 0x0000, /* R10 - Power mgmt (3) */ + 0x2000, /* R11 - Power mgmt (4) */ + 0x0E00, /* R12 - Power mgmt (5) */ + 0x0000, /* R13 - Power mgmt (6) */ + 0x0000, /* R14 - Power mgmt (7) */ + 0x0000, /* R15 */ + 0x0000, /* R16 - RTC Seconds/Minutes */ + 0x0100, /* R17 - RTC Hours/Day */ + 0x0101, /* R18 - RTC Date/Month */ + 0x1400, /* R19 - RTC Year */ + 0x0000, /* R20 - Alarm Seconds/Minutes */ + 0x0000, /* R21 - Alarm Hours/Day */ + 0x0000, /* R22 - Alarm Date/Month */ + 0x0320, /* R23 - RTC Time Control */ + 0x0000, /* R24 - System Interrupts */ + 0x0000, /* R25 - Interrupt Status 1 */ + 0x0000, /* R26 - Interrupt Status 2 */ + 0x0000, /* R27 - Power Up Interrupt Status */ + 0x0000, /* R28 - Under Voltage Interrupt status */ + 0x0000, /* R29 - Over Current Interrupt status */ + 0x0000, /* R30 - GPIO Interrupt Status */ + 0x0000, /* R31 - Comparator Interrupt Status */ + 0x3FFF, /* R32 - System Interrupts Mask */ + 0x0000, /* R33 - Interrupt Status 1 Mask */ + 0x0000, /* R34 - Interrupt Status 2 Mask */ + 0x0000, /* R35 - Power Up Interrupt Status Mask */ + 0x0000, /* R36 - Under Voltage Interrupt status Mask */ + 0x0000, /* R37 - Over Current Interrupt status Mask */ + 0x0000, /* R38 - GPIO Interrupt Status Mask */ + 0x0000, /* R39 - Comparator Interrupt Status Mask */ + 0x0040, /* R40 - Clock Control 1 */ + 0x0000, /* R41 - Clock Control 2 */ + 0x3B00, /* R42 - FLL Control 1 */ + 0x7086, /* R43 - FLL Control 2 */ + 0xC226, /* R44 - FLL Control 3 */ + 0x0000, /* R45 - FLL Control 4 */ + 0x0000, /* R46 */ + 0x0000, /* R47 */ + 0x0000, /* R48 - DAC Control */ + 0x0000, /* R49 */ + 0x00C0, /* R50 - DAC Digital Volume L */ + 0x00C0, /* R51 - DAC Digital Volume R */ + 0x0000, /* R52 */ + 0x0040, /* R53 - DAC LR Rate */ + 0x0000, /* R54 - DAC Clock Control */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x4000, /* R58 - DAC Mute */ + 0x0000, /* R59 - DAC Mute Volume */ + 0x0000, /* R60 - DAC Side */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x8000, /* R64 - ADC Control */ + 0x0000, /* R65 */ + 0x00C0, /* R66 - ADC Digital Volume L */ + 0x00C0, /* R67 - ADC Digital Volume R */ + 0x0000, /* R68 - ADC Divider */ + 0x0000, /* R69 */ + 0x0040, /* R70 - ADC LR Rate */ + 0x0000, /* R71 */ + 0x0303, /* R72 - Input Control */ + 0x0000, /* R73 - IN3 Input Control */ + 0x0000, /* R74 - Mic Bias Control */ + 0x0000, /* R75 */ + 0x0000, /* R76 - Output Control */ + 0x0000, /* R77 - Jack Detect */ + 0x0000, /* R78 - Anti Pop Control */ + 0x0000, /* R79 */ + 0x0040, /* R80 - Left Input Volume */ + 0x0040, /* R81 - Right Input Volume */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 */ + 0x0800, /* R88 - Left Mixer Control */ + 0x1000, /* R89 - Right Mixer Control */ + 0x0000, /* R90 */ + 0x0000, /* R91 */ + 0x0000, /* R92 - OUT3 Mixer Control */ + 0x0000, /* R93 - OUT4 Mixer Control */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 - Output Left Mixer Volume */ + 0x0000, /* R97 - Output Right Mixer Volume */ + 0x0000, /* R98 - Input Mixer Volume L */ + 0x0000, /* R99 - Input Mixer Volume R */ + 0x0000, /* R100 - Input Mixer Volume */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x00E4, /* R104 - LOUT1 Volume */ + 0x00E4, /* R105 - ROUT1 Volume */ + 0x00E4, /* R106 - LOUT2 Volume */ + 0x02E4, /* R107 - ROUT2 Volume */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 - BEEP Volume */ + 0x0A00, /* R112 - AI Formating */ + 0x0000, /* R113 - ADC DAC COMP */ + 0x0020, /* R114 - AI ADC Control */ + 0x0020, /* R115 - AI DAC Control */ + 0x0000, /* R116 - AIF Test */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x1FFF, /* R128 - GPIO Debounce */ + 0x0000, /* R129 - GPIO Pin pull up Control */ + 0x03FC, /* R130 - GPIO Pull down Control */ + 0x0000, /* R131 - GPIO Interrupt Mode */ + 0x0000, /* R132 */ + 0x0000, /* R133 - GPIO Control */ + 0x0A7B, /* R134 - GPIO Configuration (i/o) */ + 0x06FE, /* R135 - GPIO Pin Polarity / Type */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x1312, /* R140 - GPIO Function Select 1 */ + 0x1030, /* R141 - GPIO Function Select 2 */ + 0x2231, /* R142 - GPIO Function Select 3 */ + 0x0003, /* R143 - GPIO Function Select 4 */ + 0x0000, /* R144 - Digitiser Control (1) */ + 0x0002, /* R145 - Digitiser Control (2) */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x7000, /* R152 - AUX1 Readback */ + 0x7000, /* R153 - AUX2 Readback */ + 0x7000, /* R154 - AUX3 Readback */ + 0x7000, /* R155 - AUX4 Readback */ + 0x0000, /* R156 - USB Voltage Readback */ + 0x0000, /* R157 - LINE Voltage Readback */ + 0x0000, /* R158 - BATT Voltage Readback */ + 0x0000, /* R159 - Chip Temp Readback */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 - Generic Comparator Control */ + 0x0000, /* R164 - Generic comparator 1 */ + 0x0000, /* R165 - Generic comparator 2 */ + 0x0000, /* R166 - Generic comparator 3 */ + 0x0000, /* R167 - Generic comparator 4 */ + 0xA00F, /* R168 - Battery Charger Control 1 */ + 0x0B06, /* R169 - Battery Charger Control 2 */ + 0x0000, /* R170 - Battery Charger Control 3 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Current Sink Driver A */ + 0x0000, /* R173 - CSA Flash control */ + 0x0000, /* R174 - Current Sink Driver B */ + 0x0000, /* R175 - CSB Flash control */ + 0x0000, /* R176 - DCDC/LDO requested */ + 0x002D, /* R177 - DCDC Active options */ + 0x0000, /* R178 - DCDC Sleep options */ + 0x0025, /* R179 - Power-check comparator */ + 0x000E, /* R180 - DCDC1 Control */ + 0x0400, /* R181 - DCDC1 Timeouts */ + 0x1006, /* R182 - DCDC1 Low Power */ + 0x0018, /* R183 - DCDC2 Control */ + 0x0000, /* R184 - DCDC2 Timeouts */ + 0x0000, /* R185 */ + 0x000E, /* R186 - DCDC3 Control */ + 0x0400, /* R187 - DCDC3 Timeouts */ + 0x0006, /* R188 - DCDC3 Low Power */ + 0x0026, /* R189 - DCDC4 Control */ + 0x0400, /* R190 - DCDC4 Timeouts */ + 0x0006, /* R191 - DCDC4 Low Power */ + 0x0008, /* R192 - DCDC5 Control */ + 0x0000, /* R193 - DCDC5 Timeouts */ + 0x0000, /* R194 */ + 0x0026, /* R195 - DCDC6 Control */ + 0x0400, /* R196 - DCDC6 Timeouts */ + 0x0006, /* R197 - DCDC6 Low Power */ + 0x0000, /* R198 */ + 0x0003, /* R199 - Limit Switch Control */ + 0x001C, /* R200 - LDO1 Control */ + 0x0000, /* R201 - LDO1 Timeouts */ + 0x001C, /* R202 - LDO1 Low Power */ + 0x001C, /* R203 - LDO2 Control */ + 0x0400, /* R204 - LDO2 Timeouts */ + 0x001C, /* R205 - LDO2 Low Power */ + 0x001C, /* R206 - LDO3 Control */ + 0x0400, /* R207 - LDO3 Timeouts */ + 0x001C, /* R208 - LDO3 Low Power */ + 0x001F, /* R209 - LDO4 Control */ + 0x0400, /* R210 - LDO4 Timeouts */ + 0x001C, /* R211 - LDO4 Low Power */ + 0x0000, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 - VCC_FAULT Masks */ + 0x001F, /* R216 - Main Bandgap Control */ + 0x0000, /* R217 - OSC Control */ + 0x9000, /* R218 - RTC Tick Control */ + 0x0000, /* R219 */ + 0x4000, /* R220 - RAM BIST 1 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0000, /* R223 */ + 0x0000, /* R224 */ + 0x0000, /* R225 - DCDC/LDO status */ + 0x0000, /* R226 */ + 0x0000, /* R227 */ + 0x0000, /* R228 */ + 0x0000, /* R229 */ + 0xE000, /* R230 - GPIO Pin Status */ + 0x0000, /* R231 */ + 0x0000, /* R232 */ + 0x0000, /* R233 */ + 0x0000, /* R234 */ + 0x0000, /* R235 */ + 0x0000, /* R236 */ + 0x0000, /* R237 */ + 0x0000, /* R238 */ + 0x0000, /* R239 */ + 0x0000, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0000, /* R243 */ + 0x0000, /* R244 */ + 0x0000, /* R245 */ + 0x0000, /* R246 */ + 0x0000, /* R247 */ + 0x0000, /* R248 */ + 0x0000, /* R249 */ + 0x0000, /* R250 */ + 0x0000, /* R251 */ + 0x0000, /* R252 */ + 0x0000, /* R253 */ + 0x0000, /* R254 */ + 0x0000, /* R255 */ +}; +#endif + +#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_0 + +#undef WM8350_HAVE_CONFIG_MODE +#define WM8350_HAVE_CONFIG_MODE + +const u16 wm8351_mode0_defaults[] = { + 0x6143, /* R0 - Reset/ID */ + 0x0000, /* R1 - ID */ + 0x0001, /* R2 - Revision */ + 0x1C02, /* R3 - System Control 1 */ + 0x0004, /* R4 - System Control 2 */ + 0x0000, /* R5 - System Hibernate */ + 0x8A00, /* R6 - Interface Control */ + 0x0000, /* R7 */ + 0x8000, /* R8 - Power mgmt (1) */ + 0x0000, /* R9 - Power mgmt (2) */ + 0x0000, /* R10 - Power mgmt (3) */ + 0x2000, /* R11 - Power mgmt (4) */ + 0x0E00, /* R12 - Power mgmt (5) */ + 0x0000, /* R13 - Power mgmt (6) */ + 0x0000, /* R14 - Power mgmt (7) */ + 0x0000, /* R15 */ + 0x0000, /* R16 - RTC Seconds/Minutes */ + 0x0100, /* R17 - RTC Hours/Day */ + 0x0101, /* R18 - RTC Date/Month */ + 0x1400, /* R19 - RTC Year */ + 0x0000, /* R20 - Alarm Seconds/Minutes */ + 0x0000, /* R21 - Alarm Hours/Day */ + 0x0000, /* R22 - Alarm Date/Month */ + 0x0320, /* R23 - RTC Time Control */ + 0x0000, /* R24 - System Interrupts */ + 0x0000, /* R25 - Interrupt Status 1 */ + 0x0000, /* R26 - Interrupt Status 2 */ + 0x0000, /* R27 */ + 0x0000, /* R28 - Under Voltage Interrupt status */ + 0x0000, /* R29 - Over Current Interrupt status */ + 0x0000, /* R30 - GPIO Interrupt Status */ + 0x0000, /* R31 - Comparator Interrupt Status */ + 0x3FFF, /* R32 - System Interrupts Mask */ + 0x0000, /* R33 - Interrupt Status 1 Mask */ + 0x0000, /* R34 - Interrupt Status 2 Mask */ + 0x0000, /* R35 */ + 0x0000, /* R36 - Under Voltage Interrupt status Mask */ + 0x0000, /* R37 - Over Current Interrupt status Mask */ + 0x0000, /* R38 - GPIO Interrupt Status Mask */ + 0x0000, /* R39 - Comparator Interrupt Status Mask */ + 0x0040, /* R40 - Clock Control 1 */ + 0x0000, /* R41 - Clock Control 2 */ + 0x3A00, /* R42 - FLL Control 1 */ + 0x7086, /* R43 - FLL Control 2 */ + 0xC226, /* R44 - FLL Control 3 */ + 0x0000, /* R45 - FLL Control 4 */ + 0x0000, /* R46 */ + 0x0000, /* R47 */ + 0x0000, /* R48 - DAC Control */ + 0x0000, /* R49 */ + 0x00C0, /* R50 - DAC Digital Volume L */ + 0x00C0, /* R51 - DAC Digital Volume R */ + 0x0000, /* R52 */ + 0x0040, /* R53 - DAC LR Rate */ + 0x0000, /* R54 - DAC Clock Control */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x4000, /* R58 - DAC Mute */ + 0x0000, /* R59 - DAC Mute Volume */ + 0x0000, /* R60 - DAC Side */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x8000, /* R64 - ADC Control */ + 0x0000, /* R65 */ + 0x00C0, /* R66 - ADC Digital Volume L */ + 0x00C0, /* R67 - ADC Digital Volume R */ + 0x0000, /* R68 - ADC Divider */ + 0x0000, /* R69 */ + 0x0040, /* R70 - ADC LR Rate */ + 0x0000, /* R71 */ + 0x0303, /* R72 - Input Control */ + 0x0000, /* R73 - IN3 Input Control */ + 0x0000, /* R74 - Mic Bias Control */ + 0x0000, /* R75 */ + 0x0000, /* R76 - Output Control */ + 0x0000, /* R77 - Jack Detect */ + 0x0000, /* R78 - Anti Pop Control */ + 0x0000, /* R79 */ + 0x0040, /* R80 - Left Input Volume */ + 0x0040, /* R81 - Right Input Volume */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 */ + 0x0800, /* R88 - Left Mixer Control */ + 0x1000, /* R89 - Right Mixer Control */ + 0x0000, /* R90 */ + 0x0000, /* R91 */ + 0x0000, /* R92 - OUT3 Mixer Control */ + 0x0000, /* R93 - OUT4 Mixer Control */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 - Output Left Mixer Volume */ + 0x0000, /* R97 - Output Right Mixer Volume */ + 0x0000, /* R98 - Input Mixer Volume L */ + 0x0000, /* R99 - Input Mixer Volume R */ + 0x0000, /* R100 - Input Mixer Volume */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x00E4, /* R104 - OUT1L Volume */ + 0x00E4, /* R105 - OUT1R Volume */ + 0x00E4, /* R106 - OUT2L Volume */ + 0x02E4, /* R107 - OUT2R Volume */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 - BEEP Volume */ + 0x0A00, /* R112 - AI Formating */ + 0x0000, /* R113 - ADC DAC COMP */ + 0x0020, /* R114 - AI ADC Control */ + 0x0020, /* R115 - AI DAC Control */ + 0x0000, /* R116 */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x1FFF, /* R128 - GPIO Debounce */ + 0x0000, /* R129 - GPIO Pin pull up Control */ + 0x0000, /* R130 - GPIO Pull down Control */ + 0x0000, /* R131 - GPIO Interrupt Mode */ + 0x0000, /* R132 */ + 0x0000, /* R133 - GPIO Control */ + 0x0FFC, /* R134 - GPIO Configuration (i/o) */ + 0x0FFC, /* R135 - GPIO Pin Polarity / Type */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0013, /* R140 - GPIO Function Select 1 */ + 0x0000, /* R141 - GPIO Function Select 2 */ + 0x0000, /* R142 - GPIO Function Select 3 */ + 0x0003, /* R143 - GPIO Function Select 4 */ + 0x0000, /* R144 - Digitiser Control (1) */ + 0x0002, /* R145 - Digitiser Control (2) */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x7000, /* R152 - AUX1 Readback */ + 0x7000, /* R153 - AUX2 Readback */ + 0x7000, /* R154 - AUX3 Readback */ + 0x7000, /* R155 - AUX4 Readback */ + 0x0000, /* R156 - USB Voltage Readback */ + 0x0000, /* R157 - LINE Voltage Readback */ + 0x0000, /* R158 - BATT Voltage Readback */ + 0x0000, /* R159 - Chip Temp Readback */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 - Generic Comparator Control */ + 0x0000, /* R164 - Generic comparator 1 */ + 0x0000, /* R165 - Generic comparator 2 */ + 0x0000, /* R166 - Generic comparator 3 */ + 0x0000, /* R167 - Generic comparator 4 */ + 0xA00F, /* R168 - Battery Charger Control 1 */ + 0x0B06, /* R169 - Battery Charger Control 2 */ + 0x0000, /* R170 - Battery Charger Control 3 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Current Sink Driver A */ + 0x0000, /* R173 - CSA Flash control */ + 0x0000, /* R174 */ + 0x0000, /* R175 */ + 0x0000, /* R176 - DCDC/LDO requested */ + 0x032D, /* R177 - DCDC Active options */ + 0x0000, /* R178 - DCDC Sleep options */ + 0x0025, /* R179 - Power-check comparator */ + 0x000E, /* R180 - DCDC1 Control */ + 0x0000, /* R181 - DCDC1 Timeouts */ + 0x1006, /* R182 - DCDC1 Low Power */ + 0x0018, /* R183 - DCDC2 Control */ + 0x0000, /* R184 - DCDC2 Timeouts */ + 0x0000, /* R185 */ + 0x0000, /* R186 - DCDC3 Control */ + 0x0000, /* R187 - DCDC3 Timeouts */ + 0x0006, /* R188 - DCDC3 Low Power */ + 0x0000, /* R189 - DCDC4 Control */ + 0x0000, /* R190 - DCDC4 Timeouts */ + 0x0006, /* R191 - DCDC4 Low Power */ + 0x0008, /* R192 */ + 0x0000, /* R193 */ + 0x0000, /* R194 */ + 0x0000, /* R195 */ + 0x0000, /* R196 */ + 0x0006, /* R197 */ + 0x0000, /* R198 */ + 0x0003, /* R199 - Limit Switch Control */ + 0x001C, /* R200 - LDO1 Control */ + 0x0000, /* R201 - LDO1 Timeouts */ + 0x001C, /* R202 - LDO1 Low Power */ + 0x001B, /* R203 - LDO2 Control */ + 0x0000, /* R204 - LDO2 Timeouts */ + 0x001C, /* R205 - LDO2 Low Power */ + 0x001B, /* R206 - LDO3 Control */ + 0x0000, /* R207 - LDO3 Timeouts */ + 0x001C, /* R208 - LDO3 Low Power */ + 0x001B, /* R209 - LDO4 Control */ + 0x0000, /* R210 - LDO4 Timeouts */ + 0x001C, /* R211 - LDO4 Low Power */ + 0x0000, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 - VCC_FAULT Masks */ + 0x001F, /* R216 - Main Bandgap Control */ + 0x0000, /* R217 - OSC Control */ + 0x9000, /* R218 - RTC Tick Control */ + 0x0000, /* R219 - Security1 */ + 0x4000, /* R220 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0000, /* R223 */ + 0x0000, /* R224 - Signal overrides */ + 0x0000, /* R225 - DCDC/LDO status */ + 0x0000, /* R226 - Charger Overides/status */ + 0x0000, /* R227 - misc overrides */ + 0x0000, /* R228 - Supply overrides/status 1 */ + 0x0000, /* R229 - Supply overrides/status 2 */ + 0xE000, /* R230 - GPIO Pin Status */ + 0x0000, /* R231 - comparotor overrides */ + 0x0000, /* R232 */ + 0x0000, /* R233 - State Machine status */ + 0x1200, /* R234 - FLL Test 1 */ + 0x0000, /* R235 */ + 0x8000, /* R236 */ + 0x0000, /* R237 */ + 0x0000, /* R238 */ + 0x0000, /* R239 */ + 0x0003, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0004, /* R243 */ + 0x0300, /* R244 */ + 0x0000, /* R245 */ + 0x0200, /* R246 */ + 0x0000, /* R247 */ + 0x1000, /* R248 - DCDC1 Test Controls */ + 0x1000, /* R249 */ + 0x1000, /* R250 - DCDC3 Test Controls */ + 0x1000, /* R251 - DCDC4 Test Controls */ +}; +#endif + +#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_1 + +#undef WM8350_HAVE_CONFIG_MODE +#define WM8350_HAVE_CONFIG_MODE + +const u16 wm8351_mode1_defaults[] = { + 0x6143, /* R0 - Reset/ID */ + 0x0000, /* R1 - ID */ + 0x0001, /* R2 - Revision */ + 0x1C02, /* R3 - System Control 1 */ + 0x0204, /* R4 - System Control 2 */ + 0x0000, /* R5 - System Hibernate */ + 0x8A00, /* R6 - Interface Control */ + 0x0000, /* R7 */ + 0x8000, /* R8 - Power mgmt (1) */ + 0x0000, /* R9 - Power mgmt (2) */ + 0x0000, /* R10 - Power mgmt (3) */ + 0x2000, /* R11 - Power mgmt (4) */ + 0x0E00, /* R12 - Power mgmt (5) */ + 0x0000, /* R13 - Power mgmt (6) */ + 0x0000, /* R14 - Power mgmt (7) */ + 0x0000, /* R15 */ + 0x0000, /* R16 - RTC Seconds/Minutes */ + 0x0100, /* R17 - RTC Hours/Day */ + 0x0101, /* R18 - RTC Date/Month */ + 0x1400, /* R19 - RTC Year */ + 0x0000, /* R20 - Alarm Seconds/Minutes */ + 0x0000, /* R21 - Alarm Hours/Day */ + 0x0000, /* R22 - Alarm Date/Month */ + 0x0320, /* R23 - RTC Time Control */ + 0x0000, /* R24 - System Interrupts */ + 0x0000, /* R25 - Interrupt Status 1 */ + 0x0000, /* R26 - Interrupt Status 2 */ + 0x0000, /* R27 */ + 0x0000, /* R28 - Under Voltage Interrupt status */ + 0x0000, /* R29 - Over Current Interrupt status */ + 0x0000, /* R30 - GPIO Interrupt Status */ + 0x0000, /* R31 - Comparator Interrupt Status */ + 0x3FFF, /* R32 - System Interrupts Mask */ + 0x0000, /* R33 - Interrupt Status 1 Mask */ + 0x0000, /* R34 - Interrupt Status 2 Mask */ + 0x0000, /* R35 */ + 0x0000, /* R36 - Under Voltage Interrupt status Mask */ + 0x0000, /* R37 - Over Current Interrupt status Mask */ + 0x0000, /* R38 - GPIO Interrupt Status Mask */ + 0x0000, /* R39 - Comparator Interrupt Status Mask */ + 0x0040, /* R40 - Clock Control 1 */ + 0x0000, /* R41 - Clock Control 2 */ + 0x3A00, /* R42 - FLL Control 1 */ + 0x7086, /* R43 - FLL Control 2 */ + 0xC226, /* R44 - FLL Control 3 */ + 0x0000, /* R45 - FLL Control 4 */ + 0x0000, /* R46 */ + 0x0000, /* R47 */ + 0x0000, /* R48 - DAC Control */ + 0x0000, /* R49 */ + 0x00C0, /* R50 - DAC Digital Volume L */ + 0x00C0, /* R51 - DAC Digital Volume R */ + 0x0000, /* R52 */ + 0x0040, /* R53 - DAC LR Rate */ + 0x0000, /* R54 - DAC Clock Control */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x4000, /* R58 - DAC Mute */ + 0x0000, /* R59 - DAC Mute Volume */ + 0x0000, /* R60 - DAC Side */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x8000, /* R64 - ADC Control */ + 0x0000, /* R65 */ + 0x00C0, /* R66 - ADC Digital Volume L */ + 0x00C0, /* R67 - ADC Digital Volume R */ + 0x0000, /* R68 - ADC Divider */ + 0x0000, /* R69 */ + 0x0040, /* R70 - ADC LR Rate */ + 0x0000, /* R71 */ + 0x0303, /* R72 - Input Control */ + 0x0000, /* R73 - IN3 Input Control */ + 0x0000, /* R74 - Mic Bias Control */ + 0x0000, /* R75 */ + 0x0000, /* R76 - Output Control */ + 0x0000, /* R77 - Jack Detect */ + 0x0000, /* R78 - Anti Pop Control */ + 0x0000, /* R79 */ + 0x0040, /* R80 - Left Input Volume */ + 0x0040, /* R81 - Right Input Volume */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 */ + 0x0800, /* R88 - Left Mixer Control */ + 0x1000, /* R89 - Right Mixer Control */ + 0x0000, /* R90 */ + 0x0000, /* R91 */ + 0x0000, /* R92 - OUT3 Mixer Control */ + 0x0000, /* R93 - OUT4 Mixer Control */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 - Output Left Mixer Volume */ + 0x0000, /* R97 - Output Right Mixer Volume */ + 0x0000, /* R98 - Input Mixer Volume L */ + 0x0000, /* R99 - Input Mixer Volume R */ + 0x0000, /* R100 - Input Mixer Volume */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x00E4, /* R104 - OUT1L Volume */ + 0x00E4, /* R105 - OUT1R Volume */ + 0x00E4, /* R106 - OUT2L Volume */ + 0x02E4, /* R107 - OUT2R Volume */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 - BEEP Volume */ + 0x0A00, /* R112 - AI Formating */ + 0x0000, /* R113 - ADC DAC COMP */ + 0x0020, /* R114 - AI ADC Control */ + 0x0020, /* R115 - AI DAC Control */ + 0x0000, /* R116 */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x1FFF, /* R128 - GPIO Debounce */ + 0x0000, /* R129 - GPIO Pin pull up Control */ + 0x0000, /* R130 - GPIO Pull down Control */ + 0x0000, /* R131 - GPIO Interrupt Mode */ + 0x0000, /* R132 */ + 0x0000, /* R133 - GPIO Control */ + 0x0CFB, /* R134 - GPIO Configuration (i/o) */ + 0x0C1F, /* R135 - GPIO Pin Polarity / Type */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0300, /* R140 - GPIO Function Select 1 */ + 0x1110, /* R141 - GPIO Function Select 2 */ + 0x0013, /* R142 - GPIO Function Select 3 */ + 0x0003, /* R143 - GPIO Function Select 4 */ + 0x0000, /* R144 - Digitiser Control (1) */ + 0x0002, /* R145 - Digitiser Control (2) */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x7000, /* R152 - AUX1 Readback */ + 0x7000, /* R153 - AUX2 Readback */ + 0x7000, /* R154 - AUX3 Readback */ + 0x7000, /* R155 - AUX4 Readback */ + 0x0000, /* R156 - USB Voltage Readback */ + 0x0000, /* R157 - LINE Voltage Readback */ + 0x0000, /* R158 - BATT Voltage Readback */ + 0x0000, /* R159 - Chip Temp Readback */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 - Generic Comparator Control */ + 0x0000, /* R164 - Generic comparator 1 */ + 0x0000, /* R165 - Generic comparator 2 */ + 0x0000, /* R166 - Generic comparator 3 */ + 0x0000, /* R167 - Generic comparator 4 */ + 0xA00F, /* R168 - Battery Charger Control 1 */ + 0x0B06, /* R169 - Battery Charger Control 2 */ + 0x0000, /* R170 - Battery Charger Control 3 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Current Sink Driver A */ + 0x0000, /* R173 - CSA Flash control */ + 0x0000, /* R174 */ + 0x0000, /* R175 */ + 0x0000, /* R176 - DCDC/LDO requested */ + 0x032D, /* R177 - DCDC Active options */ + 0x0000, /* R178 - DCDC Sleep options */ + 0x0025, /* R179 - Power-check comparator */ + 0x000E, /* R180 - DCDC1 Control */ + 0x0C00, /* R181 - DCDC1 Timeouts */ + 0x1006, /* R182 - DCDC1 Low Power */ + 0x0018, /* R183 - DCDC2 Control */ + 0x0000, /* R184 - DCDC2 Timeouts */ + 0x0000, /* R185 */ + 0x0026, /* R186 - DCDC3 Control */ + 0x0400, /* R187 - DCDC3 Timeouts */ + 0x0006, /* R188 - DCDC3 Low Power */ + 0x0062, /* R189 - DCDC4 Control */ + 0x0800, /* R190 - DCDC4 Timeouts */ + 0x0006, /* R191 - DCDC4 Low Power */ + 0x0008, /* R192 */ + 0x0000, /* R193 */ + 0x0000, /* R194 */ + 0x000A, /* R195 */ + 0x1000, /* R196 */ + 0x0006, /* R197 */ + 0x0000, /* R198 */ + 0x0003, /* R199 - Limit Switch Control */ + 0x0006, /* R200 - LDO1 Control */ + 0x0000, /* R201 - LDO1 Timeouts */ + 0x001C, /* R202 - LDO1 Low Power */ + 0x0010, /* R203 - LDO2 Control */ + 0x0C00, /* R204 - LDO2 Timeouts */ + 0x001C, /* R205 - LDO2 Low Power */ + 0x001F, /* R206 - LDO3 Control */ + 0x0800, /* R207 - LDO3 Timeouts */ + 0x001C, /* R208 - LDO3 Low Power */ + 0x000A, /* R209 - LDO4 Control */ + 0x0800, /* R210 - LDO4 Timeouts */ + 0x001C, /* R211 - LDO4 Low Power */ + 0x0000, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 - VCC_FAULT Masks */ + 0x001F, /* R216 - Main Bandgap Control */ + 0x0000, /* R217 - OSC Control */ + 0x9000, /* R218 - RTC Tick Control */ + 0x0000, /* R219 - Security1 */ + 0x4000, /* R220 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0000, /* R223 */ + 0x0000, /* R224 - Signal overrides */ + 0x0000, /* R225 - DCDC/LDO status */ + 0x0000, /* R226 - Charger Overides/status */ + 0x0000, /* R227 - misc overrides */ + 0x0000, /* R228 - Supply overrides/status 1 */ + 0x0000, /* R229 - Supply overrides/status 2 */ + 0xE000, /* R230 - GPIO Pin Status */ + 0x0000, /* R231 - comparotor overrides */ + 0x0000, /* R232 */ + 0x0000, /* R233 - State Machine status */ + 0x1200, /* R234 - FLL Test 1 */ + 0x0000, /* R235 */ + 0x8000, /* R236 */ + 0x0000, /* R237 */ + 0x0000, /* R238 */ + 0x0000, /* R239 */ + 0x0003, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0004, /* R243 */ + 0x0300, /* R244 */ + 0x0000, /* R245 */ + 0x0200, /* R246 */ + 0x1000, /* R247 */ + 0x1000, /* R248 - DCDC1 Test Controls */ + 0x1000, /* R249 */ + 0x1000, /* R250 - DCDC3 Test Controls */ + 0x1000, /* R251 - DCDC4 Test Controls */ +}; +#endif + +#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_2 + +#undef WM8350_HAVE_CONFIG_MODE +#define WM8350_HAVE_CONFIG_MODE + +const u16 wm8351_mode2_defaults[] = { + 0x6143, /* R0 - Reset/ID */ + 0x0000, /* R1 - ID */ + 0x0001, /* R2 - Revision */ + 0x1C02, /* R3 - System Control 1 */ + 0x0214, /* R4 - System Control 2 */ + 0x0000, /* R5 - System Hibernate */ + 0x8A00, /* R6 - Interface Control */ + 0x0000, /* R7 */ + 0x8000, /* R8 - Power mgmt (1) */ + 0x0000, /* R9 - Power mgmt (2) */ + 0x0000, /* R10 - Power mgmt (3) */ + 0x2000, /* R11 - Power mgmt (4) */ + 0x0E00, /* R12 - Power mgmt (5) */ + 0x0000, /* R13 - Power mgmt (6) */ + 0x0000, /* R14 - Power mgmt (7) */ + 0x0000, /* R15 */ + 0x0000, /* R16 - RTC Seconds/Minutes */ + 0x0100, /* R17 - RTC Hours/Day */ + 0x0101, /* R18 - RTC Date/Month */ + 0x1400, /* R19 - RTC Year */ + 0x0000, /* R20 - Alarm Seconds/Minutes */ + 0x0000, /* R21 - Alarm Hours/Day */ + 0x0000, /* R22 - Alarm Date/Month */ + 0x0320, /* R23 - RTC Time Control */ + 0x0000, /* R24 - System Interrupts */ + 0x0000, /* R25 - Interrupt Status 1 */ + 0x0000, /* R26 - Interrupt Status 2 */ + 0x0000, /* R27 */ + 0x0000, /* R28 - Under Voltage Interrupt status */ + 0x0000, /* R29 - Over Current Interrupt status */ + 0x0000, /* R30 - GPIO Interrupt Status */ + 0x0000, /* R31 - Comparator Interrupt Status */ + 0x3FFF, /* R32 - System Interrupts Mask */ + 0x0000, /* R33 - Interrupt Status 1 Mask */ + 0x0000, /* R34 - Interrupt Status 2 Mask */ + 0x0000, /* R35 */ + 0x0000, /* R36 - Under Voltage Interrupt status Mask */ + 0x0000, /* R37 - Over Current Interrupt status Mask */ + 0x0000, /* R38 - GPIO Interrupt Status Mask */ + 0x0000, /* R39 - Comparator Interrupt Status Mask */ + 0x0040, /* R40 - Clock Control 1 */ + 0x0000, /* R41 - Clock Control 2 */ + 0x3A00, /* R42 - FLL Control 1 */ + 0x7086, /* R43 - FLL Control 2 */ + 0xC226, /* R44 - FLL Control 3 */ + 0x0000, /* R45 - FLL Control 4 */ + 0x0000, /* R46 */ + 0x0000, /* R47 */ + 0x0000, /* R48 - DAC Control */ + 0x0000, /* R49 */ + 0x00C0, /* R50 - DAC Digital Volume L */ + 0x00C0, /* R51 - DAC Digital Volume R */ + 0x0000, /* R52 */ + 0x0040, /* R53 - DAC LR Rate */ + 0x0000, /* R54 - DAC Clock Control */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x4000, /* R58 - DAC Mute */ + 0x0000, /* R59 - DAC Mute Volume */ + 0x0000, /* R60 - DAC Side */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x8000, /* R64 - ADC Control */ + 0x0000, /* R65 */ + 0x00C0, /* R66 - ADC Digital Volume L */ + 0x00C0, /* R67 - ADC Digital Volume R */ + 0x0000, /* R68 - ADC Divider */ + 0x0000, /* R69 */ + 0x0040, /* R70 - ADC LR Rate */ + 0x0000, /* R71 */ + 0x0303, /* R72 - Input Control */ + 0x0000, /* R73 - IN3 Input Control */ + 0x0000, /* R74 - Mic Bias Control */ + 0x0000, /* R75 */ + 0x0000, /* R76 - Output Control */ + 0x0000, /* R77 - Jack Detect */ + 0x0000, /* R78 - Anti Pop Control */ + 0x0000, /* R79 */ + 0x0040, /* R80 - Left Input Volume */ + 0x0040, /* R81 - Right Input Volume */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 */ + 0x0800, /* R88 - Left Mixer Control */ + 0x1000, /* R89 - Right Mixer Control */ + 0x0000, /* R90 */ + 0x0000, /* R91 */ + 0x0000, /* R92 - OUT3 Mixer Control */ + 0x0000, /* R93 - OUT4 Mixer Control */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 - Output Left Mixer Volume */ + 0x0000, /* R97 - Output Right Mixer Volume */ + 0x0000, /* R98 - Input Mixer Volume L */ + 0x0000, /* R99 - Input Mixer Volume R */ + 0x0000, /* R100 - Input Mixer Volume */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x00E4, /* R104 - OUT1L Volume */ + 0x00E4, /* R105 - OUT1R Volume */ + 0x00E4, /* R106 - OUT2L Volume */ + 0x02E4, /* R107 - OUT2R Volume */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 - BEEP Volume */ + 0x0A00, /* R112 - AI Formating */ + 0x0000, /* R113 - ADC DAC COMP */ + 0x0020, /* R114 - AI ADC Control */ + 0x0020, /* R115 - AI DAC Control */ + 0x0000, /* R116 */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x1FFF, /* R128 - GPIO Debounce */ + 0x0000, /* R129 - GPIO Pin pull up Control */ + 0x0110, /* R130 - GPIO Pull down Control */ + 0x0000, /* R131 - GPIO Interrupt Mode */ + 0x0000, /* R132 */ + 0x0000, /* R133 - GPIO Control */ + 0x09FA, /* R134 - GPIO Configuration (i/o) */ + 0x0DF6, /* R135 - GPIO Pin Polarity / Type */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x1310, /* R140 - GPIO Function Select 1 */ + 0x0003, /* R141 - GPIO Function Select 2 */ + 0x2000, /* R142 - GPIO Function Select 3 */ + 0x0000, /* R143 - GPIO Function Select 4 */ + 0x0000, /* R144 - Digitiser Control (1) */ + 0x0002, /* R145 - Digitiser Control (2) */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x7000, /* R152 - AUX1 Readback */ + 0x7000, /* R153 - AUX2 Readback */ + 0x7000, /* R154 - AUX3 Readback */ + 0x7000, /* R155 - AUX4 Readback */ + 0x0000, /* R156 - USB Voltage Readback */ + 0x0000, /* R157 - LINE Voltage Readback */ + 0x0000, /* R158 - BATT Voltage Readback */ + 0x0000, /* R159 - Chip Temp Readback */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 - Generic Comparator Control */ + 0x0000, /* R164 - Generic comparator 1 */ + 0x0000, /* R165 - Generic comparator 2 */ + 0x0000, /* R166 - Generic comparator 3 */ + 0x0000, /* R167 - Generic comparator 4 */ + 0xA00F, /* R168 - Battery Charger Control 1 */ + 0x0B06, /* R169 - Battery Charger Control 2 */ + 0x0000, /* R170 - Battery Charger Control 3 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Current Sink Driver A */ + 0x0000, /* R173 - CSA Flash control */ + 0x0000, /* R174 */ + 0x0000, /* R175 */ + 0x0000, /* R176 - DCDC/LDO requested */ + 0x032D, /* R177 - DCDC Active options */ + 0x0000, /* R178 - DCDC Sleep options */ + 0x0025, /* R179 - Power-check comparator */ + 0x001A, /* R180 - DCDC1 Control */ + 0x0800, /* R181 - DCDC1 Timeouts */ + 0x1006, /* R182 - DCDC1 Low Power */ + 0x0018, /* R183 - DCDC2 Control */ + 0x0000, /* R184 - DCDC2 Timeouts */ + 0x0000, /* R185 */ + 0x0056, /* R186 - DCDC3 Control */ + 0x0400, /* R187 - DCDC3 Timeouts */ + 0x0006, /* R188 - DCDC3 Low Power */ + 0x0026, /* R189 - DCDC4 Control */ + 0x0C00, /* R190 - DCDC4 Timeouts */ + 0x0006, /* R191 - DCDC4 Low Power */ + 0x0008, /* R192 */ + 0x0000, /* R193 */ + 0x0000, /* R194 */ + 0x0026, /* R195 */ + 0x0C00, /* R196 */ + 0x0006, /* R197 */ + 0x0000, /* R198 */ + 0x0003, /* R199 - Limit Switch Control */ + 0x001C, /* R200 - LDO1 Control */ + 0x0400, /* R201 - LDO1 Timeouts */ + 0x001C, /* R202 - LDO1 Low Power */ + 0x0010, /* R203 - LDO2 Control */ + 0x0C00, /* R204 - LDO2 Timeouts */ + 0x001C, /* R205 - LDO2 Low Power */ + 0x0015, /* R206 - LDO3 Control */ + 0x0000, /* R207 - LDO3 Timeouts */ + 0x001C, /* R208 - LDO3 Low Power */ + 0x001A, /* R209 - LDO4 Control */ + 0x0000, /* R210 - LDO4 Timeouts */ + 0x001C, /* R211 - LDO4 Low Power */ + 0x0000, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 - VCC_FAULT Masks */ + 0x001F, /* R216 - Main Bandgap Control */ + 0x0000, /* R217 - OSC Control */ + 0x9000, /* R218 - RTC Tick Control */ + 0x0000, /* R219 - Security1 */ + 0x4000, /* R220 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0000, /* R223 */ + 0x0000, /* R224 - Signal overrides */ + 0x0000, /* R225 - DCDC/LDO status */ + 0x0000, /* R226 - Charger Overides/status */ + 0x0000, /* R227 - misc overrides */ + 0x0000, /* R228 - Supply overrides/status 1 */ + 0x0000, /* R229 - Supply overrides/status 2 */ + 0xE000, /* R230 - GPIO Pin Status */ + 0x0000, /* R231 - comparotor overrides */ + 0x0000, /* R232 */ + 0x0000, /* R233 - State Machine status */ + 0x1200, /* R234 - FLL Test 1 */ + 0x0000, /* R235 */ + 0x8000, /* R236 */ + 0x0000, /* R237 */ + 0x0000, /* R238 */ + 0x0000, /* R239 */ + 0x0003, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0004, /* R243 */ + 0x0300, /* R244 */ + 0x0000, /* R245 */ + 0x0200, /* R246 */ + 0x0000, /* R247 */ + 0x1000, /* R248 - DCDC1 Test Controls */ + 0x1000, /* R249 */ + 0x1000, /* R250 - DCDC3 Test Controls */ + 0x1000, /* R251 - DCDC4 Test Controls */ +}; +#endif + +#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_3 + +#undef WM8350_HAVE_CONFIG_MODE +#define WM8350_HAVE_CONFIG_MODE + +const u16 wm8351_mode3_defaults[] = { + 0x6143, /* R0 - Reset/ID */ + 0x0000, /* R1 - ID */ + 0x0001, /* R2 - Revision */ + 0x1C02, /* R3 - System Control 1 */ + 0x0204, /* R4 - System Control 2 */ + 0x0000, /* R5 - System Hibernate */ + 0x8A00, /* R6 - Interface Control */ + 0x0000, /* R7 */ + 0x8000, /* R8 - Power mgmt (1) */ + 0x0000, /* R9 - Power mgmt (2) */ + 0x0000, /* R10 - Power mgmt (3) */ + 0x2000, /* R11 - Power mgmt (4) */ + 0x0E00, /* R12 - Power mgmt (5) */ + 0x0000, /* R13 - Power mgmt (6) */ + 0x0000, /* R14 - Power mgmt (7) */ + 0x0000, /* R15 */ + 0x0000, /* R16 - RTC Seconds/Minutes */ + 0x0100, /* R17 - RTC Hours/Day */ + 0x0101, /* R18 - RTC Date/Month */ + 0x1400, /* R19 - RTC Year */ + 0x0000, /* R20 - Alarm Seconds/Minutes */ + 0x0000, /* R21 - Alarm Hours/Day */ + 0x0000, /* R22 - Alarm Date/Month */ + 0x0320, /* R23 - RTC Time Control */ + 0x0000, /* R24 - System Interrupts */ + 0x0000, /* R25 - Interrupt Status 1 */ + 0x0000, /* R26 - Interrupt Status 2 */ + 0x0000, /* R27 */ + 0x0000, /* R28 - Under Voltage Interrupt status */ + 0x0000, /* R29 - Over Current Interrupt status */ + 0x0000, /* R30 - GPIO Interrupt Status */ + 0x0000, /* R31 - Comparator Interrupt Status */ + 0x3FFF, /* R32 - System Interrupts Mask */ + 0x0000, /* R33 - Interrupt Status 1 Mask */ + 0x0000, /* R34 - Interrupt Status 2 Mask */ + 0x0000, /* R35 */ + 0x0000, /* R36 - Under Voltage Interrupt status Mask */ + 0x0000, /* R37 - Over Current Interrupt status Mask */ + 0x0000, /* R38 - GPIO Interrupt Status Mask */ + 0x0000, /* R39 - Comparator Interrupt Status Mask */ + 0x0040, /* R40 - Clock Control 1 */ + 0x0000, /* R41 - Clock Control 2 */ + 0x3A00, /* R42 - FLL Control 1 */ + 0x7086, /* R43 - FLL Control 2 */ + 0xC226, /* R44 - FLL Control 3 */ + 0x0000, /* R45 - FLL Control 4 */ + 0x0000, /* R46 */ + 0x0000, /* R47 */ + 0x0000, /* R48 - DAC Control */ + 0x0000, /* R49 */ + 0x00C0, /* R50 - DAC Digital Volume L */ + 0x00C0, /* R51 - DAC Digital Volume R */ + 0x0000, /* R52 */ + 0x0040, /* R53 - DAC LR Rate */ + 0x0000, /* R54 - DAC Clock Control */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x4000, /* R58 - DAC Mute */ + 0x0000, /* R59 - DAC Mute Volume */ + 0x0000, /* R60 - DAC Side */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x8000, /* R64 - ADC Control */ + 0x0000, /* R65 */ + 0x00C0, /* R66 - ADC Digital Volume L */ + 0x00C0, /* R67 - ADC Digital Volume R */ + 0x0000, /* R68 - ADC Divider */ + 0x0000, /* R69 */ + 0x0040, /* R70 - ADC LR Rate */ + 0x0000, /* R71 */ + 0x0303, /* R72 - Input Control */ + 0x0000, /* R73 - IN3 Input Control */ + 0x0000, /* R74 - Mic Bias Control */ + 0x0000, /* R75 */ + 0x0000, /* R76 - Output Control */ + 0x0000, /* R77 - Jack Detect */ + 0x0000, /* R78 - Anti Pop Control */ + 0x0000, /* R79 */ + 0x0040, /* R80 - Left Input Volume */ + 0x0040, /* R81 - Right Input Volume */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 */ + 0x0800, /* R88 - Left Mixer Control */ + 0x1000, /* R89 - Right Mixer Control */ + 0x0000, /* R90 */ + 0x0000, /* R91 */ + 0x0000, /* R92 - OUT3 Mixer Control */ + 0x0000, /* R93 - OUT4 Mixer Control */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 - Output Left Mixer Volume */ + 0x0000, /* R97 - Output Right Mixer Volume */ + 0x0000, /* R98 - Input Mixer Volume L */ + 0x0000, /* R99 - Input Mixer Volume R */ + 0x0000, /* R100 - Input Mixer Volume */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x00E4, /* R104 - OUT1L Volume */ + 0x00E4, /* R105 - OUT1R Volume */ + 0x00E4, /* R106 - OUT2L Volume */ + 0x02E4, /* R107 - OUT2R Volume */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 - BEEP Volume */ + 0x0A00, /* R112 - AI Formating */ + 0x0000, /* R113 - ADC DAC COMP */ + 0x0020, /* R114 - AI ADC Control */ + 0x0020, /* R115 - AI DAC Control */ + 0x0000, /* R116 */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x1FFF, /* R128 - GPIO Debounce */ + 0x0010, /* R129 - GPIO Pin pull up Control */ + 0x0000, /* R130 - GPIO Pull down Control */ + 0x0000, /* R131 - GPIO Interrupt Mode */ + 0x0000, /* R132 */ + 0x0000, /* R133 - GPIO Control */ + 0x0BFB, /* R134 - GPIO Configuration (i/o) */ + 0x0FFD, /* R135 - GPIO Pin Polarity / Type */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0310, /* R140 - GPIO Function Select 1 */ + 0x0001, /* R141 - GPIO Function Select 2 */ + 0x2300, /* R142 - GPIO Function Select 3 */ + 0x0003, /* R143 - GPIO Function Select 4 */ + 0x0000, /* R144 - Digitiser Control (1) */ + 0x0002, /* R145 - Digitiser Control (2) */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x7000, /* R152 - AUX1 Readback */ + 0x7000, /* R153 - AUX2 Readback */ + 0x7000, /* R154 - AUX3 Readback */ + 0x7000, /* R155 - AUX4 Readback */ + 0x0000, /* R156 - USB Voltage Readback */ + 0x0000, /* R157 - LINE Voltage Readback */ + 0x0000, /* R158 - BATT Voltage Readback */ + 0x0000, /* R159 - Chip Temp Readback */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 - Generic Comparator Control */ + 0x0000, /* R164 - Generic comparator 1 */ + 0x0000, /* R165 - Generic comparator 2 */ + 0x0000, /* R166 - Generic comparator 3 */ + 0x0000, /* R167 - Generic comparator 4 */ + 0xA00F, /* R168 - Battery Charger Control 1 */ + 0x0B06, /* R169 - Battery Charger Control 2 */ + 0x0000, /* R170 - Battery Charger Control 3 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Current Sink Driver A */ + 0x0000, /* R173 - CSA Flash control */ + 0x0000, /* R174 */ + 0x0000, /* R175 */ + 0x0000, /* R176 - DCDC/LDO requested */ + 0x032D, /* R177 - DCDC Active options */ + 0x0000, /* R178 - DCDC Sleep options */ + 0x0025, /* R179 - Power-check comparator */ + 0x000E, /* R180 - DCDC1 Control */ + 0x0400, /* R181 - DCDC1 Timeouts */ + 0x1006, /* R182 - DCDC1 Low Power */ + 0x0018, /* R183 - DCDC2 Control */ + 0x0000, /* R184 - DCDC2 Timeouts */ + 0x0000, /* R185 */ + 0x0026, /* R186 - DCDC3 Control */ + 0x0800, /* R187 - DCDC3 Timeouts */ + 0x0006, /* R188 - DCDC3 Low Power */ + 0x0062, /* R189 - DCDC4 Control */ + 0x1400, /* R190 - DCDC4 Timeouts */ + 0x0006, /* R191 - DCDC4 Low Power */ + 0x0008, /* R192 */ + 0x0000, /* R193 */ + 0x0000, /* R194 */ + 0x0026, /* R195 */ + 0x0400, /* R196 */ + 0x0006, /* R197 */ + 0x0000, /* R198 */ + 0x0003, /* R199 - Limit Switch Control */ + 0x0006, /* R200 - LDO1 Control */ + 0x0C00, /* R201 - LDO1 Timeouts */ + 0x001C, /* R202 - LDO1 Low Power */ + 0x0016, /* R203 - LDO2 Control */ + 0x0000, /* R204 - LDO2 Timeouts */ + 0x001C, /* R205 - LDO2 Low Power */ + 0x0019, /* R206 - LDO3 Control */ + 0x0000, /* R207 - LDO3 Timeouts */ + 0x001C, /* R208 - LDO3 Low Power */ + 0x001A, /* R209 - LDO4 Control */ + 0x1000, /* R210 - LDO4 Timeouts */ + 0x001C, /* R211 - LDO4 Low Power */ + 0x0000, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 - VCC_FAULT Masks */ + 0x001F, /* R216 - Main Bandgap Control */ + 0x0000, /* R217 - OSC Control */ + 0x9000, /* R218 - RTC Tick Control */ + 0x0000, /* R219 - Security1 */ + 0x4000, /* R220 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0000, /* R223 */ + 0x0000, /* R224 - Signal overrides */ + 0x0000, /* R225 - DCDC/LDO status */ + 0x0000, /* R226 - Charger Overides/status */ + 0x0000, /* R227 - misc overrides */ + 0x0000, /* R228 - Supply overrides/status 1 */ + 0x0000, /* R229 - Supply overrides/status 2 */ + 0xE000, /* R230 - GPIO Pin Status */ + 0x0000, /* R231 - comparotor overrides */ + 0x0000, /* R232 */ + 0x0000, /* R233 - State Machine status */ + 0x1200, /* R234 - FLL Test 1 */ + 0x0000, /* R235 */ + 0x8000, /* R236 */ + 0x0000, /* R237 */ + 0x0000, /* R238 */ + 0x0000, /* R239 */ + 0x0003, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0004, /* R243 */ + 0x0300, /* R244 */ + 0x0000, /* R245 */ + 0x0200, /* R246 */ + 0x0000, /* R247 */ + 0x1000, /* R248 - DCDC1 Test Controls */ + 0x1000, /* R249 */ + 0x1000, /* R250 - DCDC3 Test Controls */ + 0x1000, /* R251 - DCDC4 Test Controls */ +}; +#endif + +#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_0 + +#undef WM8350_HAVE_CONFIG_MODE +#define WM8350_HAVE_CONFIG_MODE + +const u16 wm8352_mode0_defaults[] = { + 0x6143, /* R0 - Reset/ID */ + 0x0000, /* R1 - ID */ + 0x0002, /* R2 - Revision */ + 0x1C02, /* R3 - System Control 1 */ + 0x0004, /* R4 - System Control 2 */ + 0x0000, /* R5 - System Hibernate */ + 0x8A00, /* R6 - Interface Control */ + 0x0000, /* R7 */ + 0x8000, /* R8 - Power mgmt (1) */ + 0x0000, /* R9 - Power mgmt (2) */ + 0x0000, /* R10 - Power mgmt (3) */ + 0x2000, /* R11 - Power mgmt (4) */ + 0x0E00, /* R12 - Power mgmt (5) */ + 0x0000, /* R13 - Power mgmt (6) */ + 0x0000, /* R14 - Power mgmt (7) */ + 0x0000, /* R15 */ + 0x0000, /* R16 - RTC Seconds/Minutes */ + 0x0100, /* R17 - RTC Hours/Day */ + 0x0101, /* R18 - RTC Date/Month */ + 0x1400, /* R19 - RTC Year */ + 0x0000, /* R20 - Alarm Seconds/Minutes */ + 0x0000, /* R21 - Alarm Hours/Day */ + 0x0000, /* R22 - Alarm Date/Month */ + 0x0320, /* R23 - RTC Time Control */ + 0x0000, /* R24 - System Interrupts */ + 0x0000, /* R25 - Interrupt Status 1 */ + 0x0000, /* R26 - Interrupt Status 2 */ + 0x0000, /* R27 */ + 0x0000, /* R28 - Under Voltage Interrupt status */ + 0x0000, /* R29 - Over Current Interrupt status */ + 0x0000, /* R30 - GPIO Interrupt Status */ + 0x0000, /* R31 - Comparator Interrupt Status */ + 0x3FFF, /* R32 - System Interrupts Mask */ + 0x0000, /* R33 - Interrupt Status 1 Mask */ + 0x0000, /* R34 - Interrupt Status 2 Mask */ + 0x0000, /* R35 */ + 0x0000, /* R36 - Under Voltage Interrupt status Mask */ + 0x0000, /* R37 - Over Current Interrupt status Mask */ + 0x0000, /* R38 - GPIO Interrupt Status Mask */ + 0x0000, /* R39 - Comparator Interrupt Status Mask */ + 0x0040, /* R40 - Clock Control 1 */ + 0x0000, /* R41 - Clock Control 2 */ + 0x3A00, /* R42 - FLL Control 1 */ + 0x7086, /* R43 - FLL Control 2 */ + 0xC226, /* R44 - FLL Control 3 */ + 0x0000, /* R45 - FLL Control 4 */ + 0x0000, /* R46 */ + 0x0000, /* R47 */ + 0x0000, /* R48 - DAC Control */ + 0x0000, /* R49 */ + 0x00C0, /* R50 - DAC Digital Volume L */ + 0x00C0, /* R51 - DAC Digital Volume R */ + 0x0000, /* R52 */ + 0x0040, /* R53 - DAC LR Rate */ + 0x0000, /* R54 - DAC Clock Control */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x4000, /* R58 - DAC Mute */ + 0x0000, /* R59 - DAC Mute Volume */ + 0x0000, /* R60 - DAC Side */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x8000, /* R64 - ADC Control */ + 0x0000, /* R65 */ + 0x00C0, /* R66 - ADC Digital Volume L */ + 0x00C0, /* R67 - ADC Digital Volume R */ + 0x0000, /* R68 - ADC Divider */ + 0x0000, /* R69 */ + 0x0040, /* R70 - ADC LR Rate */ + 0x0000, /* R71 */ + 0x0303, /* R72 - Input Control */ + 0x0000, /* R73 - IN3 Input Control */ + 0x0000, /* R74 - Mic Bias Control */ + 0x0000, /* R75 */ + 0x0000, /* R76 - Output Control */ + 0x0000, /* R77 - Jack Detect */ + 0x0000, /* R78 - Anti Pop Control */ + 0x0000, /* R79 */ + 0x0040, /* R80 - Left Input Volume */ + 0x0040, /* R81 - Right Input Volume */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 */ + 0x0800, /* R88 - Left Mixer Control */ + 0x1000, /* R89 - Right Mixer Control */ + 0x0000, /* R90 */ + 0x0000, /* R91 */ + 0x0000, /* R92 - OUT3 Mixer Control */ + 0x0000, /* R93 - OUT4 Mixer Control */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 - Output Left Mixer Volume */ + 0x0000, /* R97 - Output Right Mixer Volume */ + 0x0000, /* R98 - Input Mixer Volume L */ + 0x0000, /* R99 - Input Mixer Volume R */ + 0x0000, /* R100 - Input Mixer Volume */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x00E4, /* R104 - OUT1L Volume */ + 0x00E4, /* R105 - OUT1R Volume */ + 0x00E4, /* R106 - OUT2L Volume */ + 0x02E4, /* R107 - OUT2R Volume */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 - BEEP Volume */ + 0x0A00, /* R112 - AI Formating */ + 0x0000, /* R113 - ADC DAC COMP */ + 0x0020, /* R114 - AI ADC Control */ + 0x0020, /* R115 - AI DAC Control */ + 0x0000, /* R116 */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x1FFF, /* R128 - GPIO Debounce */ + 0x0000, /* R129 - GPIO Pin pull up Control */ + 0x0000, /* R130 - GPIO Pull down Control */ + 0x0000, /* R131 - GPIO Interrupt Mode */ + 0x0000, /* R132 */ + 0x0000, /* R133 - GPIO Control */ + 0x0FFC, /* R134 - GPIO Configuration (i/o) */ + 0x0FFC, /* R135 - GPIO Pin Polarity / Type */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0013, /* R140 - GPIO Function Select 1 */ + 0x0000, /* R141 - GPIO Function Select 2 */ + 0x0000, /* R142 - GPIO Function Select 3 */ + 0x0003, /* R143 - GPIO Function Select 4 */ + 0x0000, /* R144 - Digitiser Control (1) */ + 0x0002, /* R145 - Digitiser Control (2) */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x7000, /* R152 - AUX1 Readback */ + 0x7000, /* R153 - AUX2 Readback */ + 0x7000, /* R154 - AUX3 Readback */ + 0x7000, /* R155 - AUX4 Readback */ + 0x0000, /* R156 - USB Voltage Readback */ + 0x0000, /* R157 - LINE Voltage Readback */ + 0x0000, /* R158 - BATT Voltage Readback */ + 0x0000, /* R159 - Chip Temp Readback */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 - Generic Comparator Control */ + 0x0000, /* R164 - Generic comparator 1 */ + 0x0000, /* R165 - Generic comparator 2 */ + 0x0000, /* R166 - Generic comparator 3 */ + 0x0000, /* R167 - Generic comparator 4 */ + 0xA00F, /* R168 - Battery Charger Control 1 */ + 0x0B06, /* R169 - Battery Charger Control 2 */ + 0x0000, /* R170 - Battery Charger Control 3 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Current Sink Driver A */ + 0x0000, /* R173 - CSA Flash control */ + 0x0000, /* R174 - Current Sink Driver B */ + 0x0000, /* R175 - CSB Flash control */ + 0x0000, /* R176 - DCDC/LDO requested */ + 0x032D, /* R177 - DCDC Active options */ + 0x0000, /* R178 - DCDC Sleep options */ + 0x0025, /* R179 - Power-check comparator */ + 0x000E, /* R180 - DCDC1 Control */ + 0x0000, /* R181 - DCDC1 Timeouts */ + 0x1006, /* R182 - DCDC1 Low Power */ + 0x0018, /* R183 - DCDC2 Control */ + 0x0000, /* R184 - DCDC2 Timeouts */ + 0x0000, /* R185 */ + 0x0000, /* R186 - DCDC3 Control */ + 0x0000, /* R187 - DCDC3 Timeouts */ + 0x0006, /* R188 - DCDC3 Low Power */ + 0x0000, /* R189 - DCDC4 Control */ + 0x0000, /* R190 - DCDC4 Timeouts */ + 0x0006, /* R191 - DCDC4 Low Power */ + 0x0008, /* R192 - DCDC5 Control */ + 0x0000, /* R193 - DCDC5 Timeouts */ + 0x0000, /* R194 */ + 0x0000, /* R195 - DCDC6 Control */ + 0x0000, /* R196 - DCDC6 Timeouts */ + 0x0006, /* R197 - DCDC6 Low Power */ + 0x0000, /* R198 */ + 0x0003, /* R199 - Limit Switch Control */ + 0x001C, /* R200 - LDO1 Control */ + 0x0000, /* R201 - LDO1 Timeouts */ + 0x001C, /* R202 - LDO1 Low Power */ + 0x001B, /* R203 - LDO2 Control */ + 0x0000, /* R204 - LDO2 Timeouts */ + 0x001C, /* R205 - LDO2 Low Power */ + 0x001B, /* R206 - LDO3 Control */ + 0x0000, /* R207 - LDO3 Timeouts */ + 0x001C, /* R208 - LDO3 Low Power */ + 0x001B, /* R209 - LDO4 Control */ + 0x0000, /* R210 - LDO4 Timeouts */ + 0x001C, /* R211 - LDO4 Low Power */ + 0x0000, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 - VCC_FAULT Masks */ + 0x001F, /* R216 - Main Bandgap Control */ + 0x0000, /* R217 - OSC Control */ + 0x9000, /* R218 - RTC Tick Control */ + 0x0000, /* R219 - Security1 */ + 0x4000, /* R220 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0000, /* R223 */ + 0x0000, /* R224 - Signal overrides */ + 0x0000, /* R225 - DCDC/LDO status */ + 0x0000, /* R226 - Charger Overides/status */ + 0x0000, /* R227 - misc overrides */ + 0x0000, /* R228 - Supply overrides/status 1 */ + 0x0000, /* R229 - Supply overrides/status 2 */ + 0xE000, /* R230 - GPIO Pin Status */ + 0x0000, /* R231 - comparotor overrides */ + 0x0000, /* R232 */ + 0x0000, /* R233 - State Machine status */ + 0x1200, /* R234 */ + 0x0000, /* R235 */ + 0x8000, /* R236 */ + 0x0000, /* R237 */ + 0x0000, /* R238 */ + 0x0000, /* R239 */ + 0x0003, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0004, /* R243 */ + 0x0300, /* R244 */ + 0x0000, /* R245 */ + 0x0200, /* R246 */ + 0x0000, /* R247 */ + 0x1000, /* R248 - DCDC1 Test Controls */ + 0x5000, /* R249 */ + 0x1000, /* R250 - DCDC3 Test Controls */ + 0x1000, /* R251 - DCDC4 Test Controls */ + 0x5100, /* R252 */ + 0x1000, /* R253 - DCDC6 Test Controls */ +}; +#endif + +#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_1 + +#undef WM8350_HAVE_CONFIG_MODE +#define WM8350_HAVE_CONFIG_MODE + +const u16 wm8352_mode1_defaults[] = { + 0x6143, /* R0 - Reset/ID */ + 0x0000, /* R1 - ID */ + 0x0002, /* R2 - Revision */ + 0x1C02, /* R3 - System Control 1 */ + 0x0204, /* R4 - System Control 2 */ + 0x0000, /* R5 - System Hibernate */ + 0x8A00, /* R6 - Interface Control */ + 0x0000, /* R7 */ + 0x8000, /* R8 - Power mgmt (1) */ + 0x0000, /* R9 - Power mgmt (2) */ + 0x0000, /* R10 - Power mgmt (3) */ + 0x2000, /* R11 - Power mgmt (4) */ + 0x0E00, /* R12 - Power mgmt (5) */ + 0x0000, /* R13 - Power mgmt (6) */ + 0x0000, /* R14 - Power mgmt (7) */ + 0x0000, /* R15 */ + 0x0000, /* R16 - RTC Seconds/Minutes */ + 0x0100, /* R17 - RTC Hours/Day */ + 0x0101, /* R18 - RTC Date/Month */ + 0x1400, /* R19 - RTC Year */ + 0x0000, /* R20 - Alarm Seconds/Minutes */ + 0x0000, /* R21 - Alarm Hours/Day */ + 0x0000, /* R22 - Alarm Date/Month */ + 0x0320, /* R23 - RTC Time Control */ + 0x0000, /* R24 - System Interrupts */ + 0x0000, /* R25 - Interrupt Status 1 */ + 0x0000, /* R26 - Interrupt Status 2 */ + 0x0000, /* R27 */ + 0x0000, /* R28 - Under Voltage Interrupt status */ + 0x0000, /* R29 - Over Current Interrupt status */ + 0x0000, /* R30 - GPIO Interrupt Status */ + 0x0000, /* R31 - Comparator Interrupt Status */ + 0x3FFF, /* R32 - System Interrupts Mask */ + 0x0000, /* R33 - Interrupt Status 1 Mask */ + 0x0000, /* R34 - Interrupt Status 2 Mask */ + 0x0000, /* R35 */ + 0x0000, /* R36 - Under Voltage Interrupt status Mask */ + 0x0000, /* R37 - Over Current Interrupt status Mask */ + 0x0000, /* R38 - GPIO Interrupt Status Mask */ + 0x0000, /* R39 - Comparator Interrupt Status Mask */ + 0x0040, /* R40 - Clock Control 1 */ + 0x0000, /* R41 - Clock Control 2 */ + 0x3A00, /* R42 - FLL Control 1 */ + 0x7086, /* R43 - FLL Control 2 */ + 0xC226, /* R44 - FLL Control 3 */ + 0x0000, /* R45 - FLL Control 4 */ + 0x0000, /* R46 */ + 0x0000, /* R47 */ + 0x0000, /* R48 - DAC Control */ + 0x0000, /* R49 */ + 0x00C0, /* R50 - DAC Digital Volume L */ + 0x00C0, /* R51 - DAC Digital Volume R */ + 0x0000, /* R52 */ + 0x0040, /* R53 - DAC LR Rate */ + 0x0000, /* R54 - DAC Clock Control */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x4000, /* R58 - DAC Mute */ + 0x0000, /* R59 - DAC Mute Volume */ + 0x0000, /* R60 - DAC Side */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x8000, /* R64 - ADC Control */ + 0x0000, /* R65 */ + 0x00C0, /* R66 - ADC Digital Volume L */ + 0x00C0, /* R67 - ADC Digital Volume R */ + 0x0000, /* R68 - ADC Divider */ + 0x0000, /* R69 */ + 0x0040, /* R70 - ADC LR Rate */ + 0x0000, /* R71 */ + 0x0303, /* R72 - Input Control */ + 0x0000, /* R73 - IN3 Input Control */ + 0x0000, /* R74 - Mic Bias Control */ + 0x0000, /* R75 */ + 0x0000, /* R76 - Output Control */ + 0x0000, /* R77 - Jack Detect */ + 0x0000, /* R78 - Anti Pop Control */ + 0x0000, /* R79 */ + 0x0040, /* R80 - Left Input Volume */ + 0x0040, /* R81 - Right Input Volume */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 */ + 0x0800, /* R88 - Left Mixer Control */ + 0x1000, /* R89 - Right Mixer Control */ + 0x0000, /* R90 */ + 0x0000, /* R91 */ + 0x0000, /* R92 - OUT3 Mixer Control */ + 0x0000, /* R93 - OUT4 Mixer Control */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 - Output Left Mixer Volume */ + 0x0000, /* R97 - Output Right Mixer Volume */ + 0x0000, /* R98 - Input Mixer Volume L */ + 0x0000, /* R99 - Input Mixer Volume R */ + 0x0000, /* R100 - Input Mixer Volume */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x00E4, /* R104 - OUT1L Volume */ + 0x00E4, /* R105 - OUT1R Volume */ + 0x00E4, /* R106 - OUT2L Volume */ + 0x02E4, /* R107 - OUT2R Volume */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 - BEEP Volume */ + 0x0A00, /* R112 - AI Formating */ + 0x0000, /* R113 - ADC DAC COMP */ + 0x0020, /* R114 - AI ADC Control */ + 0x0020, /* R115 - AI DAC Control */ + 0x0000, /* R116 */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x1FFF, /* R128 - GPIO Debounce */ + 0x0000, /* R129 - GPIO Pin pull up Control */ + 0x0000, /* R130 - GPIO Pull down Control */ + 0x0000, /* R131 - GPIO Interrupt Mode */ + 0x0000, /* R132 */ + 0x0000, /* R133 - GPIO Control */ + 0x0BFB, /* R134 - GPIO Configuration (i/o) */ + 0x0FFF, /* R135 - GPIO Pin Polarity / Type */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0300, /* R140 - GPIO Function Select 1 */ + 0x0000, /* R141 - GPIO Function Select 2 */ + 0x2300, /* R142 - GPIO Function Select 3 */ + 0x0003, /* R143 - GPIO Function Select 4 */ + 0x0000, /* R144 - Digitiser Control (1) */ + 0x0002, /* R145 - Digitiser Control (2) */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x7000, /* R152 - AUX1 Readback */ + 0x7000, /* R153 - AUX2 Readback */ + 0x7000, /* R154 - AUX3 Readback */ + 0x7000, /* R155 - AUX4 Readback */ + 0x0000, /* R156 - USB Voltage Readback */ + 0x0000, /* R157 - LINE Voltage Readback */ + 0x0000, /* R158 - BATT Voltage Readback */ + 0x0000, /* R159 - Chip Temp Readback */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 - Generic Comparator Control */ + 0x0000, /* R164 - Generic comparator 1 */ + 0x0000, /* R165 - Generic comparator 2 */ + 0x0000, /* R166 - Generic comparator 3 */ + 0x0000, /* R167 - Generic comparator 4 */ + 0xA00F, /* R168 - Battery Charger Control 1 */ + 0x0B06, /* R169 - Battery Charger Control 2 */ + 0x0000, /* R170 - Battery Charger Control 3 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Current Sink Driver A */ + 0x0000, /* R173 - CSA Flash control */ + 0x0000, /* R174 - Current Sink Driver B */ + 0x0000, /* R175 - CSB Flash control */ + 0x0000, /* R176 - DCDC/LDO requested */ + 0x032D, /* R177 - DCDC Active options */ + 0x0000, /* R178 - DCDC Sleep options */ + 0x0025, /* R179 - Power-check comparator */ + 0x0062, /* R180 - DCDC1 Control */ + 0x0400, /* R181 - DCDC1 Timeouts */ + 0x1006, /* R182 - DCDC1 Low Power */ + 0x0018, /* R183 - DCDC2 Control */ + 0x0000, /* R184 - DCDC2 Timeouts */ + 0x0000, /* R185 */ + 0x0006, /* R186 - DCDC3 Control */ + 0x0800, /* R187 - DCDC3 Timeouts */ + 0x0006, /* R188 - DCDC3 Low Power */ + 0x0006, /* R189 - DCDC4 Control */ + 0x0C00, /* R190 - DCDC4 Timeouts */ + 0x0006, /* R191 - DCDC4 Low Power */ + 0x0008, /* R192 - DCDC5 Control */ + 0x0000, /* R193 - DCDC5 Timeouts */ + 0x0000, /* R194 */ + 0x0026, /* R195 - DCDC6 Control */ + 0x1000, /* R196 - DCDC6 Timeouts */ + 0x0006, /* R197 - DCDC6 Low Power */ + 0x0000, /* R198 */ + 0x0003, /* R199 - Limit Switch Control */ + 0x0002, /* R200 - LDO1 Control */ + 0x0000, /* R201 - LDO1 Timeouts */ + 0x001C, /* R202 - LDO1 Low Power */ + 0x001A, /* R203 - LDO2 Control */ + 0x0000, /* R204 - LDO2 Timeouts */ + 0x001C, /* R205 - LDO2 Low Power */ + 0x001F, /* R206 - LDO3 Control */ + 0x0000, /* R207 - LDO3 Timeouts */ + 0x001C, /* R208 - LDO3 Low Power */ + 0x001F, /* R209 - LDO4 Control */ + 0x0000, /* R210 - LDO4 Timeouts */ + 0x001C, /* R211 - LDO4 Low Power */ + 0x0000, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 - VCC_FAULT Masks */ + 0x001F, /* R216 - Main Bandgap Control */ + 0x0000, /* R217 - OSC Control */ + 0x9000, /* R218 - RTC Tick Control */ + 0x0000, /* R219 - Security1 */ + 0x4000, /* R220 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0000, /* R223 */ + 0x0000, /* R224 - Signal overrides */ + 0x0000, /* R225 - DCDC/LDO status */ + 0x0000, /* R226 - Charger Overides/status */ + 0x0000, /* R227 - misc overrides */ + 0x0000, /* R228 - Supply overrides/status 1 */ + 0x0000, /* R229 - Supply overrides/status 2 */ + 0xE000, /* R230 - GPIO Pin Status */ + 0x0000, /* R231 - comparotor overrides */ + 0x0000, /* R232 */ + 0x0000, /* R233 - State Machine status */ + 0x1200, /* R234 */ + 0x0000, /* R235 */ + 0x8000, /* R236 */ + 0x0000, /* R237 */ + 0x0000, /* R238 */ + 0x0000, /* R239 */ + 0x0003, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0004, /* R243 */ + 0x0300, /* R244 */ + 0x0000, /* R245 */ + 0x0200, /* R246 */ + 0x0000, /* R247 */ + 0x1000, /* R248 - DCDC1 Test Controls */ + 0x5000, /* R249 */ + 0x1000, /* R250 - DCDC3 Test Controls */ + 0x1000, /* R251 - DCDC4 Test Controls */ + 0x5100, /* R252 */ + 0x1000, /* R253 - DCDC6 Test Controls */ +}; +#endif + +#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_2 + +#undef WM8350_HAVE_CONFIG_MODE +#define WM8350_HAVE_CONFIG_MODE + +const u16 wm8352_mode2_defaults[] = { + 0x6143, /* R0 - Reset/ID */ + 0x0000, /* R1 - ID */ + 0x0002, /* R2 - Revision */ + 0x1C02, /* R3 - System Control 1 */ + 0x0204, /* R4 - System Control 2 */ + 0x0000, /* R5 - System Hibernate */ + 0x8A00, /* R6 - Interface Control */ + 0x0000, /* R7 */ + 0x8000, /* R8 - Power mgmt (1) */ + 0x0000, /* R9 - Power mgmt (2) */ + 0x0000, /* R10 - Power mgmt (3) */ + 0x2000, /* R11 - Power mgmt (4) */ + 0x0E00, /* R12 - Power mgmt (5) */ + 0x0000, /* R13 - Power mgmt (6) */ + 0x0000, /* R14 - Power mgmt (7) */ + 0x0000, /* R15 */ + 0x0000, /* R16 - RTC Seconds/Minutes */ + 0x0100, /* R17 - RTC Hours/Day */ + 0x0101, /* R18 - RTC Date/Month */ + 0x1400, /* R19 - RTC Year */ + 0x0000, /* R20 - Alarm Seconds/Minutes */ + 0x0000, /* R21 - Alarm Hours/Day */ + 0x0000, /* R22 - Alarm Date/Month */ + 0x0320, /* R23 - RTC Time Control */ + 0x0000, /* R24 - System Interrupts */ + 0x0000, /* R25 - Interrupt Status 1 */ + 0x0000, /* R26 - Interrupt Status 2 */ + 0x0000, /* R27 */ + 0x0000, /* R28 - Under Voltage Interrupt status */ + 0x0000, /* R29 - Over Current Interrupt status */ + 0x0000, /* R30 - GPIO Interrupt Status */ + 0x0000, /* R31 - Comparator Interrupt Status */ + 0x3FFF, /* R32 - System Interrupts Mask */ + 0x0000, /* R33 - Interrupt Status 1 Mask */ + 0x0000, /* R34 - Interrupt Status 2 Mask */ + 0x0000, /* R35 */ + 0x0000, /* R36 - Under Voltage Interrupt status Mask */ + 0x0000, /* R37 - Over Current Interrupt status Mask */ + 0x0000, /* R38 - GPIO Interrupt Status Mask */ + 0x0000, /* R39 - Comparator Interrupt Status Mask */ + 0x0040, /* R40 - Clock Control 1 */ + 0x0000, /* R41 - Clock Control 2 */ + 0x3A00, /* R42 - FLL Control 1 */ + 0x7086, /* R43 - FLL Control 2 */ + 0xC226, /* R44 - FLL Control 3 */ + 0x0000, /* R45 - FLL Control 4 */ + 0x0000, /* R46 */ + 0x0000, /* R47 */ + 0x0000, /* R48 - DAC Control */ + 0x0000, /* R49 */ + 0x00C0, /* R50 - DAC Digital Volume L */ + 0x00C0, /* R51 - DAC Digital Volume R */ + 0x0000, /* R52 */ + 0x0040, /* R53 - DAC LR Rate */ + 0x0000, /* R54 - DAC Clock Control */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x4000, /* R58 - DAC Mute */ + 0x0000, /* R59 - DAC Mute Volume */ + 0x0000, /* R60 - DAC Side */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x8000, /* R64 - ADC Control */ + 0x0000, /* R65 */ + 0x00C0, /* R66 - ADC Digital Volume L */ + 0x00C0, /* R67 - ADC Digital Volume R */ + 0x0000, /* R68 - ADC Divider */ + 0x0000, /* R69 */ + 0x0040, /* R70 - ADC LR Rate */ + 0x0000, /* R71 */ + 0x0303, /* R72 - Input Control */ + 0x0000, /* R73 - IN3 Input Control */ + 0x0000, /* R74 - Mic Bias Control */ + 0x0000, /* R75 */ + 0x0000, /* R76 - Output Control */ + 0x0000, /* R77 - Jack Detect */ + 0x0000, /* R78 - Anti Pop Control */ + 0x0000, /* R79 */ + 0x0040, /* R80 - Left Input Volume */ + 0x0040, /* R81 - Right Input Volume */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 */ + 0x0800, /* R88 - Left Mixer Control */ + 0x1000, /* R89 - Right Mixer Control */ + 0x0000, /* R90 */ + 0x0000, /* R91 */ + 0x0000, /* R92 - OUT3 Mixer Control */ + 0x0000, /* R93 - OUT4 Mixer Control */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 - Output Left Mixer Volume */ + 0x0000, /* R97 - Output Right Mixer Volume */ + 0x0000, /* R98 - Input Mixer Volume L */ + 0x0000, /* R99 - Input Mixer Volume R */ + 0x0000, /* R100 - Input Mixer Volume */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x00E4, /* R104 - OUT1L Volume */ + 0x00E4, /* R105 - OUT1R Volume */ + 0x00E4, /* R106 - OUT2L Volume */ + 0x02E4, /* R107 - OUT2R Volume */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 - BEEP Volume */ + 0x0A00, /* R112 - AI Formating */ + 0x0000, /* R113 - ADC DAC COMP */ + 0x0020, /* R114 - AI ADC Control */ + 0x0020, /* R115 - AI DAC Control */ + 0x0000, /* R116 */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x1FFF, /* R128 - GPIO Debounce */ + 0x0000, /* R129 - GPIO Pin pull up Control */ + 0x0110, /* R130 - GPIO Pull down Control */ + 0x0000, /* R131 - GPIO Interrupt Mode */ + 0x0000, /* R132 */ + 0x0000, /* R133 - GPIO Control */ + 0x09DA, /* R134 - GPIO Configuration (i/o) */ + 0x0DD6, /* R135 - GPIO Pin Polarity / Type */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x1310, /* R140 - GPIO Function Select 1 */ + 0x0033, /* R141 - GPIO Function Select 2 */ + 0x2000, /* R142 - GPIO Function Select 3 */ + 0x0000, /* R143 - GPIO Function Select 4 */ + 0x0000, /* R144 - Digitiser Control (1) */ + 0x0002, /* R145 - Digitiser Control (2) */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x7000, /* R152 - AUX1 Readback */ + 0x7000, /* R153 - AUX2 Readback */ + 0x7000, /* R154 - AUX3 Readback */ + 0x7000, /* R155 - AUX4 Readback */ + 0x0000, /* R156 - USB Voltage Readback */ + 0x0000, /* R157 - LINE Voltage Readback */ + 0x0000, /* R158 - BATT Voltage Readback */ + 0x0000, /* R159 - Chip Temp Readback */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 - Generic Comparator Control */ + 0x0000, /* R164 - Generic comparator 1 */ + 0x0000, /* R165 - Generic comparator 2 */ + 0x0000, /* R166 - Generic comparator 3 */ + 0x0000, /* R167 - Generic comparator 4 */ + 0xA00F, /* R168 - Battery Charger Control 1 */ + 0x0B06, /* R169 - Battery Charger Control 2 */ + 0x0000, /* R170 - Battery Charger Control 3 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Current Sink Driver A */ + 0x0000, /* R173 - CSA Flash control */ + 0x0000, /* R174 - Current Sink Driver B */ + 0x0000, /* R175 - CSB Flash control */ + 0x0000, /* R176 - DCDC/LDO requested */ + 0x032D, /* R177 - DCDC Active options */ + 0x0000, /* R178 - DCDC Sleep options */ + 0x0025, /* R179 - Power-check comparator */ + 0x000E, /* R180 - DCDC1 Control */ + 0x0800, /* R181 - DCDC1 Timeouts */ + 0x1006, /* R182 - DCDC1 Low Power */ + 0x0018, /* R183 - DCDC2 Control */ + 0x0000, /* R184 - DCDC2 Timeouts */ + 0x0000, /* R185 */ + 0x0056, /* R186 - DCDC3 Control */ + 0x1800, /* R187 - DCDC3 Timeouts */ + 0x0006, /* R188 - DCDC3 Low Power */ + 0x000E, /* R189 - DCDC4 Control */ + 0x1000, /* R190 - DCDC4 Timeouts */ + 0x0006, /* R191 - DCDC4 Low Power */ + 0x0008, /* R192 - DCDC5 Control */ + 0x0000, /* R193 - DCDC5 Timeouts */ + 0x0000, /* R194 */ + 0x0026, /* R195 - DCDC6 Control */ + 0x0C00, /* R196 - DCDC6 Timeouts */ + 0x0006, /* R197 - DCDC6 Low Power */ + 0x0000, /* R198 */ + 0x0003, /* R199 - Limit Switch Control */ + 0x001C, /* R200 - LDO1 Control */ + 0x0000, /* R201 - LDO1 Timeouts */ + 0x001C, /* R202 - LDO1 Low Power */ + 0x0006, /* R203 - LDO2 Control */ + 0x0400, /* R204 - LDO2 Timeouts */ + 0x001C, /* R205 - LDO2 Low Power */ + 0x001C, /* R206 - LDO3 Control */ + 0x1400, /* R207 - LDO3 Timeouts */ + 0x001C, /* R208 - LDO3 Low Power */ + 0x001A, /* R209 - LDO4 Control */ + 0x0000, /* R210 - LDO4 Timeouts */ + 0x001C, /* R211 - LDO4 Low Power */ + 0x0000, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 - VCC_FAULT Masks */ + 0x001F, /* R216 - Main Bandgap Control */ + 0x0000, /* R217 - OSC Control */ + 0x9000, /* R218 - RTC Tick Control */ + 0x0000, /* R219 - Security1 */ + 0x4000, /* R220 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0000, /* R223 */ + 0x0000, /* R224 - Signal overrides */ + 0x0000, /* R225 - DCDC/LDO status */ + 0x0000, /* R226 - Charger Overides/status */ + 0x0000, /* R227 - misc overrides */ + 0x0000, /* R228 - Supply overrides/status 1 */ + 0x0000, /* R229 - Supply overrides/status 2 */ + 0xE000, /* R230 - GPIO Pin Status */ + 0x0000, /* R231 - comparotor overrides */ + 0x0000, /* R232 */ + 0x0000, /* R233 - State Machine status */ + 0x1200, /* R234 */ + 0x0000, /* R235 */ + 0x8000, /* R236 */ + 0x0000, /* R237 */ + 0x0000, /* R238 */ + 0x0000, /* R239 */ + 0x0003, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0004, /* R243 */ + 0x0300, /* R244 */ + 0x0000, /* R245 */ + 0x0200, /* R246 */ + 0x0000, /* R247 */ + 0x1000, /* R248 - DCDC1 Test Controls */ + 0x5000, /* R249 */ + 0x1000, /* R250 - DCDC3 Test Controls */ + 0x1000, /* R251 - DCDC4 Test Controls */ + 0x5100, /* R252 */ + 0x1000, /* R253 - DCDC6 Test Controls */ +}; +#endif + +#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_3 + +#undef WM8350_HAVE_CONFIG_MODE +#define WM8350_HAVE_CONFIG_MODE + +const u16 wm8352_mode3_defaults[] = { + 0x6143, /* R0 - Reset/ID */ + 0x0000, /* R1 - ID */ + 0x0002, /* R2 - Revision */ + 0x1C02, /* R3 - System Control 1 */ + 0x0204, /* R4 - System Control 2 */ + 0x0000, /* R5 - System Hibernate */ + 0x8A00, /* R6 - Interface Control */ + 0x0000, /* R7 */ + 0x8000, /* R8 - Power mgmt (1) */ + 0x0000, /* R9 - Power mgmt (2) */ + 0x0000, /* R10 - Power mgmt (3) */ + 0x2000, /* R11 - Power mgmt (4) */ + 0x0E00, /* R12 - Power mgmt (5) */ + 0x0000, /* R13 - Power mgmt (6) */ + 0x0000, /* R14 - Power mgmt (7) */ + 0x0000, /* R15 */ + 0x0000, /* R16 - RTC Seconds/Minutes */ + 0x0100, /* R17 - RTC Hours/Day */ + 0x0101, /* R18 - RTC Date/Month */ + 0x1400, /* R19 - RTC Year */ + 0x0000, /* R20 - Alarm Seconds/Minutes */ + 0x0000, /* R21 - Alarm Hours/Day */ + 0x0000, /* R22 - Alarm Date/Month */ + 0x0320, /* R23 - RTC Time Control */ + 0x0000, /* R24 - System Interrupts */ + 0x0000, /* R25 - Interrupt Status 1 */ + 0x0000, /* R26 - Interrupt Status 2 */ + 0x0000, /* R27 */ + 0x0000, /* R28 - Under Voltage Interrupt status */ + 0x0000, /* R29 - Over Current Interrupt status */ + 0x0000, /* R30 - GPIO Interrupt Status */ + 0x0000, /* R31 - Comparator Interrupt Status */ + 0x3FFF, /* R32 - System Interrupts Mask */ + 0x0000, /* R33 - Interrupt Status 1 Mask */ + 0x0000, /* R34 - Interrupt Status 2 Mask */ + 0x0000, /* R35 */ + 0x0000, /* R36 - Under Voltage Interrupt status Mask */ + 0x0000, /* R37 - Over Current Interrupt status Mask */ + 0x0000, /* R38 - GPIO Interrupt Status Mask */ + 0x0000, /* R39 - Comparator Interrupt Status Mask */ + 0x0040, /* R40 - Clock Control 1 */ + 0x0000, /* R41 - Clock Control 2 */ + 0x3A00, /* R42 - FLL Control 1 */ + 0x7086, /* R43 - FLL Control 2 */ + 0xC226, /* R44 - FLL Control 3 */ + 0x0000, /* R45 - FLL Control 4 */ + 0x0000, /* R46 */ + 0x0000, /* R47 */ + 0x0000, /* R48 - DAC Control */ + 0x0000, /* R49 */ + 0x00C0, /* R50 - DAC Digital Volume L */ + 0x00C0, /* R51 - DAC Digital Volume R */ + 0x0000, /* R52 */ + 0x0040, /* R53 - DAC LR Rate */ + 0x0000, /* R54 - DAC Clock Control */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x4000, /* R58 - DAC Mute */ + 0x0000, /* R59 - DAC Mute Volume */ + 0x0000, /* R60 - DAC Side */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x8000, /* R64 - ADC Control */ + 0x0000, /* R65 */ + 0x00C0, /* R66 - ADC Digital Volume L */ + 0x00C0, /* R67 - ADC Digital Volume R */ + 0x0000, /* R68 - ADC Divider */ + 0x0000, /* R69 */ + 0x0040, /* R70 - ADC LR Rate */ + 0x0000, /* R71 */ + 0x0303, /* R72 - Input Control */ + 0x0000, /* R73 - IN3 Input Control */ + 0x0000, /* R74 - Mic Bias Control */ + 0x0000, /* R75 */ + 0x0000, /* R76 - Output Control */ + 0x0000, /* R77 - Jack Detect */ + 0x0000, /* R78 - Anti Pop Control */ + 0x0000, /* R79 */ + 0x0040, /* R80 - Left Input Volume */ + 0x0040, /* R81 - Right Input Volume */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 */ + 0x0800, /* R88 - Left Mixer Control */ + 0x1000, /* R89 - Right Mixer Control */ + 0x0000, /* R90 */ + 0x0000, /* R91 */ + 0x0000, /* R92 - OUT3 Mixer Control */ + 0x0000, /* R93 - OUT4 Mixer Control */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 - Output Left Mixer Volume */ + 0x0000, /* R97 - Output Right Mixer Volume */ + 0x0000, /* R98 - Input Mixer Volume L */ + 0x0000, /* R99 - Input Mixer Volume R */ + 0x0000, /* R100 - Input Mixer Volume */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x00E4, /* R104 - OUT1L Volume */ + 0x00E4, /* R105 - OUT1R Volume */ + 0x00E4, /* R106 - OUT2L Volume */ + 0x02E4, /* R107 - OUT2R Volume */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 - BEEP Volume */ + 0x0A00, /* R112 - AI Formating */ + 0x0000, /* R113 - ADC DAC COMP */ + 0x0020, /* R114 - AI ADC Control */ + 0x0020, /* R115 - AI DAC Control */ + 0x0000, /* R116 */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x1FFF, /* R128 - GPIO Debounce */ + 0x0010, /* R129 - GPIO Pin pull up Control */ + 0x0000, /* R130 - GPIO Pull down Control */ + 0x0000, /* R131 - GPIO Interrupt Mode */ + 0x0000, /* R132 */ + 0x0000, /* R133 - GPIO Control */ + 0x0BFB, /* R134 - GPIO Configuration (i/o) */ + 0x0FFD, /* R135 - GPIO Pin Polarity / Type */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0310, /* R140 - GPIO Function Select 1 */ + 0x0001, /* R141 - GPIO Function Select 2 */ + 0x2300, /* R142 - GPIO Function Select 3 */ + 0x0003, /* R143 - GPIO Function Select 4 */ + 0x0000, /* R144 - Digitiser Control (1) */ + 0x0002, /* R145 - Digitiser Control (2) */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x7000, /* R152 - AUX1 Readback */ + 0x7000, /* R153 - AUX2 Readback */ + 0x7000, /* R154 - AUX3 Readback */ + 0x7000, /* R155 - AUX4 Readback */ + 0x0000, /* R156 - USB Voltage Readback */ + 0x0000, /* R157 - LINE Voltage Readback */ + 0x0000, /* R158 - BATT Voltage Readback */ + 0x0000, /* R159 - Chip Temp Readback */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 - Generic Comparator Control */ + 0x0000, /* R164 - Generic comparator 1 */ + 0x0000, /* R165 - Generic comparator 2 */ + 0x0000, /* R166 - Generic comparator 3 */ + 0x0000, /* R167 - Generic comparator 4 */ + 0xA00F, /* R168 - Battery Charger Control 1 */ + 0x0B06, /* R169 - Battery Charger Control 2 */ + 0x0000, /* R170 - Battery Charger Control 3 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Current Sink Driver A */ + 0x0000, /* R173 - CSA Flash control */ + 0x0000, /* R174 - Current Sink Driver B */ + 0x0000, /* R175 - CSB Flash control */ + 0x0000, /* R176 - DCDC/LDO requested */ + 0x032D, /* R177 - DCDC Active options */ + 0x0000, /* R178 - DCDC Sleep options */ + 0x0025, /* R179 - Power-check comparator */ + 0x0006, /* R180 - DCDC1 Control */ + 0x0400, /* R181 - DCDC1 Timeouts */ + 0x1006, /* R182 - DCDC1 Low Power */ + 0x0018, /* R183 - DCDC2 Control */ + 0x0000, /* R184 - DCDC2 Timeouts */ + 0x0000, /* R185 */ + 0x0050, /* R186 - DCDC3 Control */ + 0x0C00, /* R187 - DCDC3 Timeouts */ + 0x0006, /* R188 - DCDC3 Low Power */ + 0x000E, /* R189 - DCDC4 Control */ + 0x0400, /* R190 - DCDC4 Timeouts */ + 0x0006, /* R191 - DCDC4 Low Power */ + 0x0008, /* R192 - DCDC5 Control */ + 0x0000, /* R193 - DCDC5 Timeouts */ + 0x0000, /* R194 */ + 0x0029, /* R195 - DCDC6 Control */ + 0x0800, /* R196 - DCDC6 Timeouts */ + 0x0006, /* R197 - DCDC6 Low Power */ + 0x0000, /* R198 */ + 0x0003, /* R199 - Limit Switch Control */ + 0x001D, /* R200 - LDO1 Control */ + 0x1000, /* R201 - LDO1 Timeouts */ + 0x001C, /* R202 - LDO1 Low Power */ + 0x0017, /* R203 - LDO2 Control */ + 0x1000, /* R204 - LDO2 Timeouts */ + 0x001C, /* R205 - LDO2 Low Power */ + 0x0006, /* R206 - LDO3 Control */ + 0x1000, /* R207 - LDO3 Timeouts */ + 0x001C, /* R208 - LDO3 Low Power */ + 0x0010, /* R209 - LDO4 Control */ + 0x1000, /* R210 - LDO4 Timeouts */ + 0x001C, /* R211 - LDO4 Low Power */ + 0x0000, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 - VCC_FAULT Masks */ + 0x001F, /* R216 - Main Bandgap Control */ + 0x0000, /* R217 - OSC Control */ + 0x9000, /* R218 - RTC Tick Control */ + 0x0000, /* R219 - Security1 */ + 0x4000, /* R220 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0000, /* R223 */ + 0x0000, /* R224 - Signal overrides */ + 0x0000, /* R225 - DCDC/LDO status */ + 0x0000, /* R226 - Charger Overides/status */ + 0x0000, /* R227 - misc overrides */ + 0x0000, /* R228 - Supply overrides/status 1 */ + 0x0000, /* R229 - Supply overrides/status 2 */ + 0xE000, /* R230 - GPIO Pin Status */ + 0x0000, /* R231 - comparotor overrides */ + 0x0000, /* R232 */ + 0x0000, /* R233 - State Machine status */ + 0x1200, /* R234 */ + 0x0000, /* R235 */ + 0x8000, /* R236 */ + 0x0000, /* R237 */ + 0x0000, /* R238 */ + 0x0000, /* R239 */ + 0x0003, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0004, /* R243 */ + 0x0300, /* R244 */ + 0x0000, /* R245 */ + 0x0200, /* R246 */ + 0x0000, /* R247 */ + 0x1000, /* R248 - DCDC1 Test Controls */ + 0x5000, /* R249 */ + 0x1000, /* R250 - DCDC3 Test Controls */ + 0x1000, /* R251 - DCDC4 Test Controls */ + 0x5100, /* R252 */ + 0x1000, /* R253 - DCDC6 Test Controls */ +}; +#endif + +/* + * Access masks. + */ + +const struct wm8350_reg_access wm8350_reg_io_map[] = { + /* read write volatile */ + { 0xFFFF, 0xFFFF, 0xFFFF }, /* R0 - Reset/ID */ + { 0x7CFF, 0x0C00, 0x7FFF }, /* R1 - ID */ + { 0x007F, 0x0000, 0x0000 }, /* R2 - ROM Mask ID */ + { 0xBE3B, 0xBE3B, 0x8000 }, /* R3 - System Control 1 */ + { 0xFEF7, 0xFEF7, 0xF800 }, /* R4 - System Control 2 */ + { 0x80FF, 0x80FF, 0x8000 }, /* R5 - System Hibernate */ + { 0xFB0E, 0xFB0E, 0x0000 }, /* R6 - Interface Control */ + { 0x0000, 0x0000, 0x0000 }, /* R7 */ + { 0xE537, 0xE537, 0xFFFF }, /* R8 - Power mgmt (1) */ + { 0x0FF3, 0x0FF3, 0xFFFF }, /* R9 - Power mgmt (2) */ + { 0x008F, 0x008F, 0xFFFF }, /* R10 - Power mgmt (3) */ + { 0x6D3C, 0x6D3C, 0xFFFF }, /* R11 - Power mgmt (4) */ + { 0x1F8F, 0x1F8F, 0xFFFF }, /* R12 - Power mgmt (5) */ + { 0x8F3F, 0x8F3F, 0xFFFF }, /* R13 - Power mgmt (6) */ + { 0x0003, 0x0003, 0xFFFF }, /* R14 - Power mgmt (7) */ + { 0x0000, 0x0000, 0x0000 }, /* R15 */ + { 0x7F7F, 0x7F7F, 0xFFFF }, /* R16 - RTC Seconds/Minutes */ + { 0x073F, 0x073F, 0xFFFF }, /* R17 - RTC Hours/Day */ + { 0x1F3F, 0x1F3F, 0xFFFF }, /* R18 - RTC Date/Month */ + { 0x3FFF, 0x00FF, 0xFFFF }, /* R19 - RTC Year */ + { 0x7F7F, 0x7F7F, 0x0000 }, /* R20 - Alarm Seconds/Minutes */ + { 0x0F3F, 0x0F3F, 0x0000 }, /* R21 - Alarm Hours/Day */ + { 0x1F3F, 0x1F3F, 0x0000 }, /* R22 - Alarm Date/Month */ + { 0xEF7F, 0xEA7F, 0xFFFF }, /* R23 - RTC Time Control */ + { 0x3BFF, 0x0000, 0xFFFF }, /* R24 - System Interrupts */ + { 0xFEE7, 0x0000, 0xFFFF }, /* R25 - Interrupt Status 1 */ + { 0x35FF, 0x0000, 0xFFFF }, /* R26 - Interrupt Status 2 */ + { 0x0F3F, 0x0000, 0xFFFF }, /* R27 - Power Up Interrupt Status */ + { 0x0F3F, 0x0000, 0xFFFF }, /* R28 - Under Voltage Interrupt status */ + { 0x8000, 0x0000, 0xFFFF }, /* R29 - Over Current Interrupt status */ + { 0x1FFF, 0x0000, 0xFFFF }, /* R30 - GPIO Interrupt Status */ + { 0xEF7F, 0x0000, 0xFFFF }, /* R31 - Comparator Interrupt Status */ + { 0x3FFF, 0x3FFF, 0x0000 }, /* R32 - System Interrupts Mask */ + { 0xFEE7, 0xFEE7, 0x0000 }, /* R33 - Interrupt Status 1 Mask */ + { 0xF5FF, 0xF5FF, 0x0000 }, /* R34 - Interrupt Status 2 Mask */ + { 0x0F3F, 0x0F3F, 0x0000 }, /* R35 - Power Up Interrupt Status Mask */ + { 0x0F3F, 0x0F3F, 0x0000 }, /* R36 - Under Voltage Int status Mask */ + { 0x8000, 0x8000, 0x0000 }, /* R37 - Over Current Int status Mask */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R38 - GPIO Interrupt Status Mask */ + { 0xEF7F, 0xEF7F, 0x0000 }, /* R39 - Comparator IntStatus Mask */ + { 0xC9F7, 0xC9F7, 0xFFFF }, /* R40 - Clock Control 1 */ + { 0x8001, 0x8001, 0x0000 }, /* R41 - Clock Control 2 */ + { 0xFFF7, 0xFFF7, 0xFFFF }, /* R42 - FLL Control 1 */ + { 0xFBFF, 0xFBFF, 0x0000 }, /* R43 - FLL Control 2 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R44 - FLL Control 3 */ + { 0x0033, 0x0033, 0x0000 }, /* R45 - FLL Control 4 */ + { 0x0000, 0x0000, 0x0000 }, /* R46 */ + { 0x0000, 0x0000, 0x0000 }, /* R47 */ + { 0x3033, 0x3033, 0x0000 }, /* R48 - DAC Control */ + { 0x0000, 0x0000, 0x0000 }, /* R49 */ + { 0x81FF, 0x81FF, 0xFFFF }, /* R50 - DAC Digital Volume L */ + { 0x81FF, 0x81FF, 0xFFFF }, /* R51 - DAC Digital Volume R */ + { 0x0000, 0x0000, 0x0000 }, /* R52 */ + { 0x0FFF, 0x0FFF, 0xFFFF }, /* R53 - DAC LR Rate */ + { 0x0017, 0x0017, 0x0000 }, /* R54 - DAC Clock Control */ + { 0x0000, 0x0000, 0x0000 }, /* R55 */ + { 0x0000, 0x0000, 0x0000 }, /* R56 */ + { 0x0000, 0x0000, 0x0000 }, /* R57 */ + { 0x4000, 0x4000, 0x0000 }, /* R58 - DAC Mute */ + { 0x7000, 0x7000, 0x0000 }, /* R59 - DAC Mute Volume */ + { 0x3C00, 0x3C00, 0x0000 }, /* R60 - DAC Side */ + { 0x0000, 0x0000, 0x0000 }, /* R61 */ + { 0x0000, 0x0000, 0x0000 }, /* R62 */ + { 0x0000, 0x0000, 0x0000 }, /* R63 */ + { 0x8303, 0x8303, 0xFFFF }, /* R64 - ADC Control */ + { 0x0000, 0x0000, 0x0000 }, /* R65 */ + { 0x81FF, 0x81FF, 0xFFFF }, /* R66 - ADC Digital Volume L */ + { 0x81FF, 0x81FF, 0xFFFF }, /* R67 - ADC Digital Volume R */ + { 0x0FFF, 0x0FFF, 0x0000 }, /* R68 - ADC Divider */ + { 0x0000, 0x0000, 0x0000 }, /* R69 */ + { 0x0FFF, 0x0FFF, 0xFFFF }, /* R70 - ADC LR Rate */ + { 0x0000, 0x0000, 0x0000 }, /* R71 */ + { 0x0707, 0x0707, 0xFFFF }, /* R72 - Input Control */ + { 0xC0C0, 0xC0C0, 0xFFFF }, /* R73 - IN3 Input Control */ + { 0xC09F, 0xC09F, 0xFFFF }, /* R74 - Mic Bias Control */ + { 0x0000, 0x0000, 0x0000 }, /* R75 */ + { 0x0F15, 0x0F15, 0xFFFF }, /* R76 - Output Control */ + { 0xC000, 0xC000, 0xFFFF }, /* R77 - Jack Detect */ + { 0x03FF, 0x03FF, 0x0000 }, /* R78 - Anti Pop Control */ + { 0x0000, 0x0000, 0x0000 }, /* R79 */ + { 0xE1FC, 0xE1FC, 0x8000 }, /* R80 - Left Input Volume */ + { 0xE1FC, 0xE1FC, 0x8000 }, /* R81 - Right Input Volume */ + { 0x0000, 0x0000, 0x0000 }, /* R82 */ + { 0x0000, 0x0000, 0x0000 }, /* R83 */ + { 0x0000, 0x0000, 0x0000 }, /* R84 */ + { 0x0000, 0x0000, 0x0000 }, /* R85 */ + { 0x0000, 0x0000, 0x0000 }, /* R86 */ + { 0x0000, 0x0000, 0x0000 }, /* R87 */ + { 0x9807, 0x9807, 0xFFFF }, /* R88 - Left Mixer Control */ + { 0x980B, 0x980B, 0xFFFF }, /* R89 - Right Mixer Control */ + { 0x0000, 0x0000, 0x0000 }, /* R90 */ + { 0x0000, 0x0000, 0x0000 }, /* R91 */ + { 0x8909, 0x8909, 0xFFFF }, /* R92 - OUT3 Mixer Control */ + { 0x9E07, 0x9E07, 0xFFFF }, /* R93 - OUT4 Mixer Control */ + { 0x0000, 0x0000, 0x0000 }, /* R94 */ + { 0x0000, 0x0000, 0x0000 }, /* R95 */ + { 0x0EEE, 0x0EEE, 0x0000 }, /* R96 - Output Left Mixer Volume */ + { 0xE0EE, 0xE0EE, 0x0000 }, /* R97 - Output Right Mixer Volume */ + { 0x0E0F, 0x0E0F, 0x0000 }, /* R98 - Input Mixer Volume L */ + { 0xE0E1, 0xE0E1, 0x0000 }, /* R99 - Input Mixer Volume R */ + { 0x800E, 0x800E, 0x0000 }, /* R100 - Input Mixer Volume */ + { 0x0000, 0x0000, 0x0000 }, /* R101 */ + { 0x0000, 0x0000, 0x0000 }, /* R102 */ + { 0x0000, 0x0000, 0x0000 }, /* R103 */ + { 0xE1FC, 0xE1FC, 0xFFFF }, /* R104 - LOUT1 Volume */ + { 0xE1FC, 0xE1FC, 0xFFFF }, /* R105 - ROUT1 Volume */ + { 0xE1FC, 0xE1FC, 0xFFFF }, /* R106 - LOUT2 Volume */ + { 0xE7FC, 0xE7FC, 0xFFFF }, /* R107 - ROUT2 Volume */ + { 0x0000, 0x0000, 0x0000 }, /* R108 */ + { 0x0000, 0x0000, 0x0000 }, /* R109 */ + { 0x0000, 0x0000, 0x0000 }, /* R110 */ + { 0x80E0, 0x80E0, 0xFFFF }, /* R111 - BEEP Volume */ + { 0xBF00, 0xBF00, 0x0000 }, /* R112 - AI Formating */ + { 0x00F1, 0x00F1, 0x0000 }, /* R113 - ADC DAC COMP */ + { 0x00F8, 0x00F8, 0x0000 }, /* R114 - AI ADC Control */ + { 0x40FB, 0x40FB, 0x0000 }, /* R115 - AI DAC Control */ + { 0x7C30, 0x7C30, 0x0000 }, /* R116 - AIF Test */ + { 0x0000, 0x0000, 0x0000 }, /* R117 */ + { 0x0000, 0x0000, 0x0000 }, /* R118 */ + { 0x0000, 0x0000, 0x0000 }, /* R119 */ + { 0x0000, 0x0000, 0x0000 }, /* R120 */ + { 0x0000, 0x0000, 0x0000 }, /* R121 */ + { 0x0000, 0x0000, 0x0000 }, /* R122 */ + { 0x0000, 0x0000, 0x0000 }, /* R123 */ + { 0x0000, 0x0000, 0x0000 }, /* R124 */ + { 0x0000, 0x0000, 0x0000 }, /* R125 */ + { 0x0000, 0x0000, 0x0000 }, /* R126 */ + { 0x0000, 0x0000, 0x0000 }, /* R127 */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R128 - GPIO Debounce */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R129 - GPIO Pin pull up Control */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R130 - GPIO Pull down Control */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R131 - GPIO Interrupt Mode */ + { 0x0000, 0x0000, 0x0000 }, /* R132 */ + { 0x00C0, 0x00C0, 0x0000 }, /* R133 - GPIO Control */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R134 - GPIO Configuration (i/o) */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R135 - GPIO Pin Polarity / Type */ + { 0x0000, 0x0000, 0x0000 }, /* R136 */ + { 0x0000, 0x0000, 0x0000 }, /* R137 */ + { 0x0000, 0x0000, 0x0000 }, /* R138 */ + { 0x0000, 0x0000, 0x0000 }, /* R139 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R140 - GPIO Function Select 1 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R141 - GPIO Function Select 2 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R142 - GPIO Function Select 3 */ + { 0x000F, 0x000F, 0x0000 }, /* R143 - GPIO Function Select 4 */ + { 0xF0FF, 0xF0FF, 0xA000 }, /* R144 - Digitiser Control (1) */ + { 0x3707, 0x3707, 0x0000 }, /* R145 - Digitiser Control (2) */ + { 0x0000, 0x0000, 0x0000 }, /* R146 */ + { 0x0000, 0x0000, 0x0000 }, /* R147 */ + { 0x0000, 0x0000, 0x0000 }, /* R148 */ + { 0x0000, 0x0000, 0x0000 }, /* R149 */ + { 0x0000, 0x0000, 0x0000 }, /* R150 */ + { 0x0000, 0x0000, 0x0000 }, /* R151 */ + { 0x7FFF, 0x7000, 0xFFFF }, /* R152 - AUX1 Readback */ + { 0x7FFF, 0x7000, 0xFFFF }, /* R153 - AUX2 Readback */ + { 0x7FFF, 0x7000, 0xFFFF }, /* R154 - AUX3 Readback */ + { 0x7FFF, 0x7000, 0xFFFF }, /* R155 - AUX4 Readback */ + { 0x0FFF, 0x0000, 0xFFFF }, /* R156 - USB Voltage Readback */ + { 0x0FFF, 0x0000, 0xFFFF }, /* R157 - LINE Voltage Readback */ + { 0x0FFF, 0x0000, 0xFFFF }, /* R158 - BATT Voltage Readback */ + { 0x0FFF, 0x0000, 0xFFFF }, /* R159 - Chip Temp Readback */ + { 0x0000, 0x0000, 0x0000 }, /* R160 */ + { 0x0000, 0x0000, 0x0000 }, /* R161 */ + { 0x0000, 0x0000, 0x0000 }, /* R162 */ + { 0x000F, 0x000F, 0x0000 }, /* R163 - Generic Comparator Control */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R164 - Generic comparator 1 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R165 - Generic comparator 2 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R166 - Generic comparator 3 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R167 - Generic comparator 4 */ + { 0xBFFF, 0xBFFF, 0x8000 }, /* R168 - Battery Charger Control 1 */ + { 0xFFFF, 0x4FFF, 0xB000 }, /* R169 - Battery Charger Control 2 */ + { 0x007F, 0x007F, 0x0000 }, /* R170 - Battery Charger Control 3 */ + { 0x0000, 0x0000, 0x0000 }, /* R171 */ + { 0x903F, 0x903F, 0xFFFF }, /* R172 - Current Sink Driver A */ + { 0xE333, 0xE333, 0xFFFF }, /* R173 - CSA Flash control */ + { 0x903F, 0x903F, 0xFFFF }, /* R174 - Current Sink Driver B */ + { 0xE333, 0xE333, 0xFFFF }, /* R175 - CSB Flash control */ + { 0x8F3F, 0x8F3F, 0xFFFF }, /* R176 - DCDC/LDO requested */ + { 0x332D, 0x332D, 0x0000 }, /* R177 - DCDC Active options */ + { 0x002D, 0x002D, 0x0000 }, /* R178 - DCDC Sleep options */ + { 0x5177, 0x5177, 0x8000 }, /* R179 - Power-check comparator */ + { 0x047F, 0x047F, 0x0000 }, /* R180 - DCDC1 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R181 - DCDC1 Timeouts */ + { 0x737F, 0x737F, 0x0000 }, /* R182 - DCDC1 Low Power */ + { 0x535B, 0x535B, 0x0000 }, /* R183 - DCDC2 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R184 - DCDC2 Timeouts */ + { 0x0000, 0x0000, 0x0000 }, /* R185 */ + { 0x047F, 0x047F, 0x0000 }, /* R186 - DCDC3 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R187 - DCDC3 Timeouts */ + { 0x737F, 0x737F, 0x0000 }, /* R188 - DCDC3 Low Power */ + { 0x047F, 0x047F, 0x0000 }, /* R189 - DCDC4 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R190 - DCDC4 Timeouts */ + { 0x737F, 0x737F, 0x0000 }, /* R191 - DCDC4 Low Power */ + { 0x535B, 0x535B, 0x0000 }, /* R192 - DCDC5 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R193 - DCDC5 Timeouts */ + { 0x0000, 0x0000, 0x0000 }, /* R194 */ + { 0x047F, 0x047F, 0x0000 }, /* R195 - DCDC6 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R196 - DCDC6 Timeouts */ + { 0x737F, 0x737F, 0x0000 }, /* R197 - DCDC6 Low Power */ + { 0x0000, 0x0000, 0x0000 }, /* R198 */ + { 0xFFD3, 0xFFD3, 0x0000 }, /* R199 - Limit Switch Control */ + { 0x441F, 0x441F, 0x0000 }, /* R200 - LDO1 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R201 - LDO1 Timeouts */ + { 0x331F, 0x331F, 0x0000 }, /* R202 - LDO1 Low Power */ + { 0x441F, 0x441F, 0x0000 }, /* R203 - LDO2 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R204 - LDO2 Timeouts */ + { 0x331F, 0x331F, 0x0000 }, /* R205 - LDO2 Low Power */ + { 0x441F, 0x441F, 0x0000 }, /* R206 - LDO3 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R207 - LDO3 Timeouts */ + { 0x331F, 0x331F, 0x0000 }, /* R208 - LDO3 Low Power */ + { 0x441F, 0x441F, 0x0000 }, /* R209 - LDO4 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R210 - LDO4 Timeouts */ + { 0x331F, 0x331F, 0x0000 }, /* R211 - LDO4 Low Power */ + { 0x0000, 0x0000, 0x0000 }, /* R212 */ + { 0x0000, 0x0000, 0x0000 }, /* R213 */ + { 0x0000, 0x0000, 0x0000 }, /* R214 */ + { 0x8F3F, 0x8F3F, 0x0000 }, /* R215 - VCC_FAULT Masks */ + { 0xFF3F, 0xE03F, 0x0000 }, /* R216 - Main Bandgap Control */ + { 0xEF2F, 0xE02F, 0x0000 }, /* R217 - OSC Control */ + { 0xF3FF, 0xB3FF, 0xc000 }, /* R218 - RTC Tick Control */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R219 - Security */ + { 0x09FF, 0x01FF, 0x0000 }, /* R220 - RAM BIST 1 */ + { 0x0000, 0x0000, 0x0000 }, /* R221 */ + { 0xFFFF, 0xFFFF, 0xFFFF }, /* R222 */ + { 0xFFFF, 0xFFFF, 0xFFFF }, /* R223 */ + { 0x0000, 0x0000, 0x0000 }, /* R224 */ + { 0x8F3F, 0x0000, 0xFFFF }, /* R225 - DCDC/LDO status */ + { 0x0000, 0x0000, 0xFFFF }, /* R226 - Charger status */ + { 0x34FE, 0x0000, 0xFFFF }, /* R227 */ + { 0x0000, 0x0000, 0x0000 }, /* R228 */ + { 0x0000, 0x0000, 0x0000 }, /* R229 */ + { 0xFFFF, 0x1FFF, 0xFFFF }, /* R230 - GPIO Pin Status */ + { 0xFFFF, 0x1FFF, 0xFFFF }, /* R231 */ + { 0xFFFF, 0x1FFF, 0xFFFF }, /* R232 */ + { 0xFFFF, 0x1FFF, 0xFFFF }, /* R233 */ + { 0x0000, 0x0000, 0x0000 }, /* R234 */ + { 0x0000, 0x0000, 0x0000 }, /* R235 */ + { 0x0000, 0x0000, 0x0000 }, /* R236 */ + { 0x0000, 0x0000, 0x0000 }, /* R237 */ + { 0x0000, 0x0000, 0x0000 }, /* R238 */ + { 0x0000, 0x0000, 0x0000 }, /* R239 */ + { 0x0000, 0x0000, 0x0000 }, /* R240 */ + { 0x0000, 0x0000, 0x0000 }, /* R241 */ + { 0x0000, 0x0000, 0x0000 }, /* R242 */ + { 0x0000, 0x0000, 0x0000 }, /* R243 */ + { 0x0000, 0x0000, 0x0000 }, /* R244 */ + { 0x0000, 0x0000, 0x0000 }, /* R245 */ + { 0x0000, 0x0000, 0x0000 }, /* R246 */ + { 0x0000, 0x0000, 0x0000 }, /* R247 */ + { 0xFFFF, 0x0010, 0xFFFF }, /* R248 */ + { 0x0000, 0x0000, 0x0000 }, /* R249 */ + { 0xFFFF, 0x0010, 0xFFFF }, /* R250 */ + { 0xFFFF, 0x0010, 0xFFFF }, /* R251 */ + { 0x0000, 0x0000, 0x0000 }, /* R252 */ + { 0xFFFF, 0x0010, 0xFFFF }, /* R253 */ + { 0x0000, 0x0000, 0x0000 }, /* R254 */ + { 0x0000, 0x0000, 0x0000 }, /* R255 */ +}; diff --git a/drivers/mfd/wm8400-core.c b/drivers/mfd/wm8400-core.c new file mode 100644 index 00000000000..ecfc8bbe89b --- /dev/null +++ b/drivers/mfd/wm8400-core.c @@ -0,0 +1,474 @@ +/* + * Core driver for WM8400. + * + * Copyright 2008 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.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. + * + */ + +#include <linux/bug.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/mfd/core.h> +#include <linux/mfd/wm8400-private.h> +#include <linux/mfd/wm8400-audio.h> + +static struct { + u16 readable; /* Mask of readable bits */ + u16 writable; /* Mask of writable bits */ + u16 vol; /* Mask of volatile bits */ + int is_codec; /* Register controlled by codec reset */ + u16 default_val; /* Value on reset */ +} reg_data[] = { + { 0xFFFF, 0xFFFF, 0x0000, 0, 0x6172 }, /* R0 */ + { 0x7000, 0x0000, 0x8000, 0, 0x0000 }, /* R1 */ + { 0xFF17, 0xFF17, 0x0000, 0, 0x0000 }, /* R2 */ + { 0xEBF3, 0xEBF3, 0x0000, 1, 0x6000 }, /* R3 */ + { 0x3CF3, 0x3CF3, 0x0000, 1, 0x0000 }, /* R4 */ + { 0xF1F8, 0xF1F8, 0x0000, 1, 0x4050 }, /* R5 */ + { 0xFC1F, 0xFC1F, 0x0000, 1, 0x4000 }, /* R6 */ + { 0xDFDE, 0xDFDE, 0x0000, 1, 0x01C8 }, /* R7 */ + { 0xFCFC, 0xFCFC, 0x0000, 1, 0x0000 }, /* R8 */ + { 0xEFFF, 0xEFFF, 0x0000, 1, 0x0040 }, /* R9 */ + { 0xEFFF, 0xEFFF, 0x0000, 1, 0x0040 }, /* R10 */ + { 0x27F7, 0x27F7, 0x0000, 1, 0x0004 }, /* R11 */ + { 0x01FF, 0x01FF, 0x0000, 1, 0x00C0 }, /* R12 */ + { 0x01FF, 0x01FF, 0x0000, 1, 0x00C0 }, /* R13 */ + { 0x1FEF, 0x1FEF, 0x0000, 1, 0x0000 }, /* R14 */ + { 0x0163, 0x0163, 0x0000, 1, 0x0100 }, /* R15 */ + { 0x01FF, 0x01FF, 0x0000, 1, 0x00C0 }, /* R16 */ + { 0x01FF, 0x01FF, 0x0000, 1, 0x00C0 }, /* R17 */ + { 0x1FFF, 0x0FFF, 0x0000, 1, 0x0000 }, /* R18 */ + { 0xFFFF, 0xFFFF, 0x0000, 1, 0x1000 }, /* R19 */ + { 0xFFFF, 0xFFFF, 0x0000, 1, 0x1010 }, /* R20 */ + { 0xFFFF, 0xFFFF, 0x0000, 1, 0x1010 }, /* R21 */ + { 0x0FDD, 0x0FDD, 0x0000, 1, 0x8000 }, /* R22 */ + { 0x1FFF, 0x1FFF, 0x0000, 1, 0x0800 }, /* R23 */ + { 0x0000, 0x01DF, 0x0000, 1, 0x008B }, /* R24 */ + { 0x0000, 0x01DF, 0x0000, 1, 0x008B }, /* R25 */ + { 0x0000, 0x01DF, 0x0000, 1, 0x008B }, /* R26 */ + { 0x0000, 0x01DF, 0x0000, 1, 0x008B }, /* R27 */ + { 0x0000, 0x01FF, 0x0000, 1, 0x0000 }, /* R28 */ + { 0x0000, 0x01FF, 0x0000, 1, 0x0000 }, /* R29 */ + { 0x0000, 0x0077, 0x0000, 1, 0x0066 }, /* R30 */ + { 0x0000, 0x0033, 0x0000, 1, 0x0022 }, /* R31 */ + { 0x0000, 0x01FF, 0x0000, 1, 0x0079 }, /* R32 */ + { 0x0000, 0x01FF, 0x0000, 1, 0x0079 }, /* R33 */ + { 0x0000, 0x0003, 0x0000, 1, 0x0003 }, /* R34 */ + { 0x0000, 0x01FF, 0x0000, 1, 0x0003 }, /* R35 */ + { 0x0000, 0x0000, 0x0000, 0, 0x0000 }, /* R36 */ + { 0x0000, 0x003F, 0x0000, 1, 0x0100 }, /* R37 */ + { 0x0000, 0x0000, 0x0000, 0, 0x0000 }, /* R38 */ + { 0x0000, 0x000F, 0x0000, 0, 0x0000 }, /* R39 */ + { 0x0000, 0x00FF, 0x0000, 1, 0x0000 }, /* R40 */ + { 0x0000, 0x01B7, 0x0000, 1, 0x0000 }, /* R41 */ + { 0x0000, 0x01B7, 0x0000, 1, 0x0000 }, /* R42 */ + { 0x0000, 0x01FF, 0x0000, 1, 0x0000 }, /* R43 */ + { 0x0000, 0x01FF, 0x0000, 1, 0x0000 }, /* R44 */ + { 0x0000, 0x00FD, 0x0000, 1, 0x0000 }, /* R45 */ + { 0x0000, 0x00FD, 0x0000, 1, 0x0000 }, /* R46 */ + { 0x0000, 0x01FF, 0x0000, 1, 0x0000 }, /* R47 */ + { 0x0000, 0x01FF, 0x0000, 1, 0x0000 }, /* R48 */ + { 0x0000, 0x01FF, 0x0000, 1, 0x0000 }, /* R49 */ + { 0x0000, 0x01FF, 0x0000, 1, 0x0000 }, /* R50 */ + { 0x0000, 0x01B3, 0x0000, 1, 0x0180 }, /* R51 */ + { 0x0000, 0x0077, 0x0000, 1, 0x0000 }, /* R52 */ + { 0x0000, 0x0077, 0x0000, 1, 0x0000 }, /* R53 */ + { 0x0000, 0x00FF, 0x0000, 1, 0x0000 }, /* R54 */ + { 0x0000, 0x0001, 0x0000, 1, 0x0000 }, /* R55 */ + { 0x0000, 0x003F, 0x0000, 1, 0x0000 }, /* R56 */ + { 0x0000, 0x004F, 0x0000, 1, 0x0000 }, /* R57 */ + { 0x0000, 0x00FD, 0x0000, 1, 0x0000 }, /* R58 */ + { 0x0000, 0x0000, 0x0000, 0, 0x0000 }, /* R59 */ + { 0x1FFF, 0x1FFF, 0x0000, 1, 0x0000 }, /* R60 */ + { 0xFFFF, 0xFFFF, 0x0000, 1, 0x0000 }, /* R61 */ + { 0x03FF, 0x03FF, 0x0000, 1, 0x0000 }, /* R62 */ + { 0x007F, 0x007F, 0x0000, 1, 0x0000 }, /* R63 */ + { 0x0000, 0x0000, 0x0000, 0, 0x0000 }, /* R64 */ + { 0xDFFF, 0xDFFF, 0x0000, 0, 0x0000 }, /* R65 */ + { 0xDFFF, 0xDFFF, 0x0000, 0, 0x0000 }, /* R66 */ + { 0xDFFF, 0xDFFF, 0x0000, 0, 0x0000 }, /* R67 */ + { 0xDFFF, 0xDFFF, 0x0000, 0, 0x0000 }, /* R68 */ + { 0x0000, 0x0000, 0x0000, 0, 0x0000 }, /* R69 */ + { 0xFFFF, 0xFFFF, 0x0000, 0, 0x4400 }, /* R70 */ + { 0x23FF, 0x23FF, 0x0000, 0, 0x0000 }, /* R71 */ + { 0xFFFF, 0xFFFF, 0x0000, 0, 0x4400 }, /* R72 */ + { 0x23FF, 0x23FF, 0x0000, 0, 0x0000 }, /* R73 */ + { 0x0000, 0x0000, 0x0000, 0, 0x0000 }, /* R74 */ + { 0x000E, 0x000E, 0x0000, 0, 0x0008 }, /* R75 */ + { 0xE00F, 0xE00F, 0x0000, 0, 0x0000 }, /* R76 */ + { 0x0000, 0x0000, 0x0000, 0, 0x0000 }, /* R77 */ + { 0x03C0, 0x03C0, 0x0000, 0, 0x02C0 }, /* R78 */ + { 0xFFFF, 0x0000, 0xffff, 0, 0x0000 }, /* R79 */ + { 0xFFFF, 0xFFFF, 0x0000, 0, 0x0000 }, /* R80 */ + { 0xFFFF, 0x0000, 0xffff, 0, 0x0000 }, /* R81 */ + { 0x2BFF, 0x0000, 0xffff, 0, 0x0000 }, /* R82 */ + { 0x0000, 0x0000, 0x0000, 0, 0x0000 }, /* R83 */ + { 0x80FF, 0x80FF, 0x0000, 0, 0x00ff }, /* R84 */ +}; + +static int wm8400_read(struct wm8400 *wm8400, u8 reg, int num_regs, u16 *dest) +{ + int i, ret = 0; + + BUG_ON(reg + num_regs - 1 > ARRAY_SIZE(wm8400->reg_cache)); + + /* If there are any volatile reads then read back the entire block */ + for (i = reg; i < reg + num_regs; i++) + if (reg_data[i].vol) { + ret = wm8400->read_dev(wm8400->io_data, reg, + num_regs, dest); + if (ret != 0) + return ret; + for (i = 0; i < num_regs; i++) + dest[i] = be16_to_cpu(dest[i]); + + return 0; + } + + /* Otherwise use the cache */ + memcpy(dest, &wm8400->reg_cache[reg], num_regs * sizeof(u16)); + + return 0; +} + +static int wm8400_write(struct wm8400 *wm8400, u8 reg, int num_regs, + u16 *src) +{ + int ret, i; + + BUG_ON(reg + num_regs - 1 > ARRAY_SIZE(wm8400->reg_cache)); + + for (i = 0; i < num_regs; i++) { + BUG_ON(!reg_data[reg + i].writable); + wm8400->reg_cache[reg + i] = src[i]; + src[i] = cpu_to_be16(src[i]); + } + + /* Do the actual I/O */ + ret = wm8400->write_dev(wm8400->io_data, reg, num_regs, src); + if (ret != 0) + return -EIO; + + return 0; +} + +/** + * wm8400_reg_read - Single register read + * + * @wm8400: Pointer to wm8400 control structure + * @reg: Register to read + * + * @return Read value + */ +u16 wm8400_reg_read(struct wm8400 *wm8400, u8 reg) +{ + u16 val; + + mutex_lock(&wm8400->io_lock); + + wm8400_read(wm8400, reg, 1, &val); + + mutex_unlock(&wm8400->io_lock); + + return val; +} +EXPORT_SYMBOL_GPL(wm8400_reg_read); + +int wm8400_block_read(struct wm8400 *wm8400, u8 reg, int count, u16 *data) +{ + int ret; + + mutex_lock(&wm8400->io_lock); + + ret = wm8400_read(wm8400, reg, count, data); + + mutex_unlock(&wm8400->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm8400_block_read); + +/** + * wm8400_set_bits - Bitmask write + * + * @wm8400: Pointer to wm8400 control structure + * @reg: Register to access + * @mask: Mask of bits to change + * @val: Value to set for masked bits + */ +int wm8400_set_bits(struct wm8400 *wm8400, u8 reg, u16 mask, u16 val) +{ + u16 tmp; + int ret; + + mutex_lock(&wm8400->io_lock); + + ret = wm8400_read(wm8400, reg, 1, &tmp); + tmp = (tmp & ~mask) | val; + if (ret == 0) + ret = wm8400_write(wm8400, reg, 1, &tmp); + + mutex_unlock(&wm8400->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm8400_set_bits); + +/** + * wm8400_reset_codec_reg_cache - Reset cached codec registers to + * their default values. + */ +void wm8400_reset_codec_reg_cache(struct wm8400 *wm8400) +{ + int i; + + mutex_lock(&wm8400->io_lock); + + /* Reset all codec registers to their initial value */ + for (i = 0; i < ARRAY_SIZE(wm8400->reg_cache); i++) + if (reg_data[i].is_codec) + wm8400->reg_cache[i] = reg_data[i].default_val; + + mutex_unlock(&wm8400->io_lock); +} +EXPORT_SYMBOL_GPL(wm8400_reset_codec_reg_cache); + +static int wm8400_register_codec(struct wm8400 *wm8400) +{ + struct mfd_cell cell = { + .name = "wm8400-codec", + .driver_data = wm8400, + }; + + return mfd_add_devices(wm8400->dev, -1, &cell, 1, NULL, 0); +} + +/* + * wm8400_init - Generic initialisation + * + * The WM8400 can be configured as either an I2C or SPI device. Probe + * functions for each bus set up the accessors then call into this to + * set up the device itself. + */ +static int wm8400_init(struct wm8400 *wm8400, + struct wm8400_platform_data *pdata) +{ + u16 reg; + int ret, i; + + mutex_init(&wm8400->io_lock); + + dev_set_drvdata(wm8400->dev, wm8400); + + /* Check that this is actually a WM8400 */ + ret = wm8400->read_dev(wm8400->io_data, WM8400_RESET_ID, 1, ®); + if (ret != 0) { + dev_err(wm8400->dev, "Chip ID register read failed\n"); + return -EIO; + } + if (be16_to_cpu(reg) != reg_data[WM8400_RESET_ID].default_val) { + dev_err(wm8400->dev, "Device is not a WM8400, ID is %x\n", + be16_to_cpu(reg)); + return -ENODEV; + } + + /* We don't know what state the hardware is in and since this + * is a PMIC we can't reset it safely so initialise the register + * cache from the hardware. + */ + ret = wm8400->read_dev(wm8400->io_data, 0, + ARRAY_SIZE(wm8400->reg_cache), + wm8400->reg_cache); + if (ret != 0) { + dev_err(wm8400->dev, "Register cache read failed\n"); + return -EIO; + } + for (i = 0; i < ARRAY_SIZE(wm8400->reg_cache); i++) + wm8400->reg_cache[i] = be16_to_cpu(wm8400->reg_cache[i]); + + /* If the codec is in reset use hard coded values */ + if (!(wm8400->reg_cache[WM8400_POWER_MANAGEMENT_1] & WM8400_CODEC_ENA)) + for (i = 0; i < ARRAY_SIZE(wm8400->reg_cache); i++) + if (reg_data[i].is_codec) + wm8400->reg_cache[i] = reg_data[i].default_val; + + ret = wm8400_read(wm8400, WM8400_ID, 1, ®); + if (ret != 0) { + dev_err(wm8400->dev, "ID register read failed: %d\n", ret); + return ret; + } + reg = (reg & WM8400_CHIP_REV_MASK) >> WM8400_CHIP_REV_SHIFT; + dev_info(wm8400->dev, "WM8400 revision %x\n", reg); + + ret = wm8400_register_codec(wm8400); + if (ret != 0) { + dev_err(wm8400->dev, "Failed to register codec\n"); + goto err_children; + } + + if (pdata && pdata->platform_init) { + ret = pdata->platform_init(wm8400->dev); + if (ret != 0) { + dev_err(wm8400->dev, "Platform init failed: %d\n", + ret); + goto err_children; + } + } else + dev_warn(wm8400->dev, "No platform initialisation supplied\n"); + + return 0; + +err_children: + mfd_remove_devices(wm8400->dev); + return ret; +} + +static void wm8400_release(struct wm8400 *wm8400) +{ + mfd_remove_devices(wm8400->dev); +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static int wm8400_i2c_read(void *io_data, char reg, int count, u16 *dest) +{ + struct i2c_client *i2c = io_data; + struct i2c_msg xfer[2]; + int ret; + + /* Write register */ + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = count * sizeof(u16); + xfer[1].buf = (u8 *)dest; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret == 2) + ret = 0; + else if (ret >= 0) + ret = -EIO; + + return ret; +} + +static int wm8400_i2c_write(void *io_data, char reg, int count, const u16 *src) +{ + struct i2c_client *i2c = io_data; + u8 *msg; + int ret; + + /* We add 1 byte for device register - ideally I2C would gather. */ + msg = kmalloc((count * sizeof(u16)) + 1, GFP_KERNEL); + if (msg == NULL) + return -ENOMEM; + + msg[0] = reg; + memcpy(&msg[1], src, count * sizeof(u16)); + + ret = i2c_master_send(i2c, msg, (count * sizeof(u16)) + 1); + + if (ret == (count * 2) + 1) + ret = 0; + else if (ret >= 0) + ret = -EIO; + + kfree(msg); + + return ret; +} + +static int wm8400_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8400 *wm8400; + int ret; + + wm8400 = kzalloc(sizeof(struct wm8400), GFP_KERNEL); + if (wm8400 == NULL) { + ret = -ENOMEM; + goto err; + } + + wm8400->io_data = i2c; + wm8400->read_dev = wm8400_i2c_read; + wm8400->write_dev = wm8400_i2c_write; + wm8400->dev = &i2c->dev; + i2c_set_clientdata(i2c, wm8400); + + ret = wm8400_init(wm8400, i2c->dev.platform_data); + if (ret != 0) + goto struct_err; + + return 0; + +struct_err: + i2c_set_clientdata(i2c, NULL); + kfree(wm8400); +err: + return ret; +} + +static int wm8400_i2c_remove(struct i2c_client *i2c) +{ + struct wm8400 *wm8400 = i2c_get_clientdata(i2c); + + wm8400_release(wm8400); + i2c_set_clientdata(i2c, NULL); + kfree(wm8400); + + return 0; +} + +static const struct i2c_device_id wm8400_i2c_id[] = { + { "wm8400", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8400_i2c_id); + +static struct i2c_driver wm8400_i2c_driver = { + .driver = { + .name = "WM8400", + .owner = THIS_MODULE, + }, + .probe = wm8400_i2c_probe, + .remove = wm8400_i2c_remove, + .id_table = wm8400_i2c_id, +}; +#endif + +static int __init wm8400_module_init(void) +{ + int ret = -ENODEV; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&wm8400_i2c_driver); + if (ret != 0) + pr_err("Failed to register I2C driver: %d\n", ret); +#endif + + return ret; +} +subsys_initcall(wm8400_module_init); + +static void __exit wm8400_module_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8400_i2c_driver); +#endif +} +module_exit(wm8400_module_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); |