diff options
Diffstat (limited to 'drivers/mfd')
58 files changed, 8423 insertions, 1943 deletions
diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 893fc1ba6ead..31ca55548ef9 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -1144,17 +1144,15 @@ static int pm860x_probe(struct i2c_client *client, return -ENOMEM; ret = pm860x_dt_init(node, &client->dev, pdata); if (ret) - goto err; + return ret; } else if (!pdata) { pr_info("No platform data in %s!\n", __func__); return -EINVAL; } chip = kzalloc(sizeof(struct pm860x_chip), GFP_KERNEL); - if (chip == NULL) { - ret = -ENOMEM; - goto err; - } + if (chip == NULL) + return -ENOMEM; chip->id = verify_addr(client); chip->regmap = regmap_init_i2c(client, &pm860x_regmap_config); @@ -1194,10 +1192,6 @@ static int pm860x_probe(struct i2c_client *client, pm860x_device_init(chip, pdata); return 0; -err: - if (node) - devm_kfree(&client->dev, pdata); - return ret; } static int pm860x_remove(struct i2c_client *client) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c346941a2515..d9aed1593e5d 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -10,19 +10,240 @@ config MFD_CORE select IRQ_DOMAIN default n -config MFD_88PM860X - bool "Support Marvell 88PM8606/88PM8607" +config MFD_CS5535 + tristate "AMD CS5535 and CS5536 southbridge core functions" + select MFD_CORE + depends on PCI && X86 + ---help--- + This is the core driver for CS5535/CS5536 MFD functions. This is + necessary for using the board's GPIO and MFGPT functionality. + +config MFD_AS3711 + bool "AMS AS3711" + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ depends on I2C=y && GENERIC_HARDIRQS + help + Support for the AS3711 PMIC from AMS + +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_AAT2870_CORE + bool "AnalogicTech AAT2870" + select MFD_CORE + depends on I2C=y && GPIOLIB && GENERIC_HARDIRQS + help + If you say yes here you get support for the AAT2870. + 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_CROS_EC + tristate "ChromeOS Embedded Controller" + select MFD_CORE + help + If you say Y here you get support for the ChromeOS Embedded + Controller (EC) providing keyboard, battery and power services. + You also ned to enable the driver for the bus you are using. The + protocol for talking to the EC is defined by the bus driver. + +config MFD_CROS_EC_I2C + tristate "ChromeOS Embedded Controller (I2C)" + depends on MFD_CROS_EC && I2C + + help + If you say Y here, you get support for talking to the ChromeOS + EC through an I2C bus. This uses a simple byte-level protocol with + a checksum. Failing accesses will be retried three times to + improve reliability. + +config MFD_CROS_EC_SPI + tristate "ChromeOS Embedded Controller (SPI)" + depends on MFD_CROS_EC && SPI + + ---help--- + If you say Y here, you get support for talking to the ChromeOS EC + through a SPI bus, using a byte-level protocol. Since the EC's + response time cannot be guaranteed, we support ignoring + 'pre-amble' bytes before the response actually starts. + +config MFD_ASIC3 + bool "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 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_DA9052 + bool + select MFD_CORE + +config MFD_DA9052_SPI + bool "Dialog Semiconductor DA9052/53 PMIC variants with SPI" + select REGMAP_SPI + select REGMAP_IRQ + select PMIC_DA9052 + depends on SPI_MASTER=y && GENERIC_HARDIRQS + help + Support for the Dialog Semiconductor DA9052 PMIC + when controlled using SPI. 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_DA9052_I2C + bool "Dialog Semiconductor DA9052/53 PMIC variants with I2C" select REGMAP_I2C + select REGMAP_IRQ + select PMIC_DA9052 + depends on I2C=y && GENERIC_HARDIRQS + help + Support for the Dialog Semiconductor DA9052 PMIC + when controlled using I2C. 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_DA9055 + bool "Dialog Semiconductor DA9055 PMIC Support" + select REGMAP_I2C + select REGMAP_IRQ select MFD_CORE + depends on I2C=y && GENERIC_HARDIRQS help - This supports for Marvell 88PM8606/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. + Say yes here for support of Dialog Semiconductor DA9055. This is + a Power Management IC. This driver provides common support for + accessing the device as well as the I2C interface to the chip itself. + Additional drivers must be enabled in order to use the functionality + of the device. + + This driver can be built as a module. If built as a module it will be + called "da9055" + +config MFD_MC13783 + tristate + +config MFD_MC13XXX + tristate + depends on (SPI_MASTER || I2C) && GENERIC_HARDIRQS + select MFD_CORE + select MFD_MC13783 + help + Enable support for the Freescale MC13783 and MC13892 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_MC13XXX_SPI + tristate "Freescale MC13783 and MC13892 SPI interface" + depends on SPI_MASTER && GENERIC_HARDIRQS + select REGMAP_SPI + select MFD_MC13XXX + help + Select this if your MC13xxx is connected via an SPI bus. + +config MFD_MC13XXX_I2C + tristate "Freescale MC13892 I2C interface" + depends on I2C && GENERIC_HARDIRQS + select REGMAP_I2C + select MFD_MC13XXX + help + Select this if your MC13xxx is connected via an I2C bus. + +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 + depends on GENERIC_HARDIRQS + 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 HTC_I2CPLD + bool "HTC I2C PLD chip support" + depends on I2C=y && GPIOLIB + help + If you say yes here you get support for the supposed CPLD + found on omap850 HTC devices like the HTC Wizard and HTC Herald. + This device provides input and output GPIOs through an I2C + interface to one or more sub-chips. + +config LPC_ICH + tristate "Intel ICH LPC" + depends on PCI && GENERIC_HARDIRQS + select MFD_CORE + help + The LPC bridge function of the Intel ICH provides support for + many functional units. This driver provides needed support for + other drivers to control these functions, currently GPIO and + watchdog. + +config LPC_SCH + tristate "Intel SCH LPC" + depends on PCI && GENERIC_HARDIRQS + select MFD_CORE + help + LPC bridge function of the Intel SCH provides support for + System Management Bus and General Purpose I/O. + +config MFD_INTEL_MSIC + bool "Intel MSIC" + depends on INTEL_SCU_IPC + select MFD_CORE + help + Select this option to enable access to Intel MSIC (Avatele + Passage) chip. This chip embeds audio, battery, GPIO, etc. + devices used in Intel Medfield platforms. + +config MFD_JANZ_CMODIO + tristate "Janz CMOD-IO PCI MODULbus Carrier Board" + select MFD_CORE + depends on PCI && GENERIC_HARDIRQS + help + This is the core driver for the Janz CMOD-IO PCI MODULbus + carrier board. This device is a PCI to MODULbus bridge which may + host many different types of MODULbus daughterboards, including + CAN and GPIO controllers. + +config MFD_JZ4740_ADC + bool "Janz JZ4740 ADC core" + select MFD_CORE + select GENERIC_IRQ_CHIP + depends on MACH_JZ4740 + help + Say yes here if you want support for the ADC unit in the JZ4740 SoC. + This driver is necessary for jz4740-battery and jz4740-hwmon driver. config MFD_88PM800 - tristate "Support Marvell 88PM800" + tristate "Marvell 88PM800" depends on I2C=y && GENERIC_HARDIRQS select REGMAP_I2C select REGMAP_IRQ @@ -34,7 +255,7 @@ config MFD_88PM800 battery-charger under the corresponding menus. config MFD_88PM805 - tristate "Support Marvell 88PM805" + tristate "Marvell 88PM805" depends on I2C=y && GENERIC_HARDIRQS select REGMAP_I2C select REGMAP_IRQ @@ -45,8 +266,242 @@ config MFD_88PM805 components like codec device, headset/Mic device under the corresponding menus. +config MFD_88PM860X + bool "Marvell 88PM8606/88PM8607" + depends on I2C=y && GENERIC_HARDIRQS + select REGMAP_I2C + select MFD_CORE + help + This supports for Marvell 88PM8606/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 MFD_MAX77686 + bool "Maxim Semiconductor MAX77686 PMIC Support" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + select REGMAP_I2C + select IRQ_DOMAIN + help + Say yes here to support for Maxim Semiconductor MAX77686. + This is a Power Management IC with RTC on chip. + 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_MAX77693 + bool "Maxim Semiconductor MAX77693 PMIC Support" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + select REGMAP_I2C + help + Say yes here to support for Maxim Semiconductor MAX77693. + This is a companion Power Management IC with Flash, Haptic, Charger, + and MUIC(Micro USB Interface Controller) controls on chip. + 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_MAX8907 + tristate "Maxim Semiconductor MAX8907 PMIC Support" + select MFD_CORE + depends on I2C=y && GENERIC_HARDIRQS + select REGMAP_I2C + select REGMAP_IRQ + help + Say yes here to support for Maxim Semiconductor MAX8907. This is + a Power Management IC. 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_MAX8925 + bool "Maxim Semiconductor MAX8925 PMIC Support" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + help + Say yes here to support for Maxim Semiconductor MAX8925. This is + a Power Management IC. 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_MAX8997 + bool "Maxim Semiconductor MAX8997/8966 PMIC Support" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + select IRQ_DOMAIN + help + Say yes here to support for Maxim Semiconductor MAX8997/8966. + This is a Power Management IC with RTC, Flash, Fuel Gauge, Haptic, + MUIC controls on chip. + 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_MAX8998 + bool "Maxim Semiconductor MAX8998/National LP3974 PMIC Support" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + help + Say yes here to support for Maxim Semiconductor MAX8998 and + National Semiconductor LP3974. This is a Power Management IC. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the functionality + of the device. + +config EZX_PCAP + bool "Motorola EZXPCAP 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_VIPERBOARD + tristate "Nano River Technologies Viperboard" + select MFD_CORE + depends on USB && GENERIC_HARDIRQS + default n + help + Say yes here if you want support for Nano River Technologies + Viperboard. + There are mfd cell drivers available for i2c master, adc and + both gpios found on the board. The spi part does not yet + have a driver. + You need to select the mfd cell drivers separately. + The drivers do not support all features the board exposes. + +config MFD_RETU + tristate "Nokia Retu and Tahvo multi-function device" + select MFD_CORE + depends on I2C && GENERIC_HARDIRQS + select REGMAP_IRQ + help + Retu and Tahvo are a multi-function devices found on Nokia + Internet Tablets (770, N800 and N810). + +config MFD_PCF50633 + tristate "NXP PCF50633" + depends on I2C + select REGMAP_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 PCF50633_ADC + tristate "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 "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 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 MFD_PM8XXX + tristate + +config MFD_PM8921_CORE + tristate "Qualcomm PM8921 PMIC chip" + depends on SSBI && BROKEN + select MFD_CORE + select MFD_PM8XXX + help + If you say yes to this option, support will be included for the + built-in PM8921 PMIC chip. + + This is required if your board has a PM8921 and uses its features, + such as: MPPs, GPIOs, regulators, interrupts, and PWM. + + Say M here if you want to include support for PM8921 chip as a module. + This will build a module called "pm8921-core". + +config MFD_PM8XXX_IRQ + bool "Qualcomm PM8xxx IRQ features" + depends on MFD_PM8XXX + default y if MFD_PM8XXX + help + This is the IRQ driver for Qualcomm PM 8xxx PMIC chips. + + This is required to use certain other PM 8xxx features, such as GPIO + and MPP. + +config MFD_RDC321X + tristate "RDC R-321x southbridge" + select MFD_CORE + depends on PCI && GENERIC_HARDIRQS + help + Say yes here if you want to have support for the RDC R-321x SoC + southbridge which provides access to GPIOs and Watchdog using the + southbridge PCI device configuration space. + +config MFD_RTSX_PCI + tristate "Realtek PCI-E card reader" + depends on PCI && GENERIC_HARDIRQS + select MFD_CORE + help + This supports for Realtek PCI-Express card reader including rts5209, + rts5229, rtl8411, etc. Realtek card reader supports access to many + types of memory cards, such as Memory Stick, Memory Stick Pro, + Secure Digital and MultiMediaCard. + +config MFD_RC5T583 + bool "Ricoh RC5T583 Power Management system device" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + select REGMAP_I2C + help + Select this option to get support for the RICOH583 Power + Management system device. + This driver provides common support for accessing the device + through i2c interface. The device supports multiple sub-devices + like GPIO, interrupts, RTC, LDO and DCDC regulators, onkey. + Additional drivers must be enabled in order to use the + different functionality of the device. + +config MFD_SEC_CORE + bool "SAMSUNG Electronics PMIC Series Support" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + Support for the Samsung Electronics MFD series. + 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_SI476X_CORE + tristate "Silicon Laboratories 4761/64/68 AM/FM radio." + depends on I2C + select MFD_CORE + select REGMAP_I2C + help + This is the core driver for the SI476x series of AM/FM + radio. This MFD driver connects the radio-si476x V4L2 module + and the si476x audio codec. + + To compile this driver as a module, choose M here: the + module will be called si476x-core. + config MFD_SM501 - tristate "Support for Silicon Motion SM501" + tristate "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 @@ -63,46 +518,147 @@ config MFD_SM501_GPIO lines on the SM501. The platform data is used to supply the base number for the first GPIO line to register. -config MFD_RTSX_PCI - tristate "Support for Realtek PCI-E card reader" - depends on PCI && GENERIC_HARDIRQS +config MFD_SMSC + bool "SMSC ECE1099 series chips" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + select REGMAP_I2C + help + If you say yes here you get support for the + ece1099 chips from SMSC. + + To compile this driver as a module, choose M here: the + module will be called smsc. + +config ABX500_CORE + bool "ST-Ericsson ABX500 Mixed Signal Circuit register functions" + default y if ARCH_U300 || ARCH_U8500 + help + Say yes here if you have the ABX500 Mixed Signal IC family + chips. This core driver expose register access functions. + Functionality specific drivers using these functions can + remain unchanged when IC changes. Binding of the functions to + actual register access is done by the IC core driver. + +config AB3100_CORE + bool "ST-Ericsson AB3100 Mixed Signal Circuit core functions" + depends on I2C=y && ABX500_CORE && GENERIC_HARDIRQS select MFD_CORE + default y if ARCH_U300 help - This supports for Realtek PCI-Express card reader including rts5209, - rts5229, rtl8411, etc. Realtek card reader supports access to many - types of memory cards, such as Memory Stick, Memory Stick Pro, - Secure Digital and MultiMediaCard. + 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 MFD_ASIC3 - bool "Support for Compaq ASIC3" - depends on GENERIC_HARDIRQS && GPIOLIB && ARM +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 AB8500_CORE + bool "ST-Ericsson AB8500 Mixed Signal Power Management chip" + depends on GENERIC_HARDIRQS && ABX500_CORE && MFD_DB8500_PRCMU + select POWER_SUPPLY select MFD_CORE - ---help--- - This driver supports the ASIC3 multifunction chip found on many - PDAs (mainly iPAQ and HTC based ones) + select IRQ_DOMAIN + help + Select this option to enable access to AB8500 power management + chip. This connects to U8500 either on the SSP/SPI bus (deprecated + since hardware version v1.0) or the I2C bus via PRCMU. It also adds + the irq_chip parts for handling the Mixed Signal chip events. + This chip embeds various other multimedia funtionalities as well. -config MFD_DAVINCI_VOICECODEC - tristate +config AB8500_DEBUG + bool "Enable debug info via debugfs" + depends on AB8500_CORE && DEBUG_FS + default y if DEBUG_FS + help + Select this option if you want debug information using the debug + filesystem, debugfs. + +config AB8500_GPADC + bool "ST-Ericsson AB8500 GPADC driver" + depends on AB8500_CORE && REGULATOR_AB8500 + default y + help + AB8500 GPADC driver used to convert Acc and battery/ac/usb voltage + +config MFD_DB8500_PRCMU + bool "ST-Ericsson DB8500 Power Reset Control Management Unit" + depends on UX500_SOC_DB8500 select MFD_CORE + help + Select this option to enable support for the DB8500 Power Reset + and Control Management Unit. This is basically an autonomous + system controller running an XP70 microprocessor, which is accessed + through a register map. -config MFD_DM355EVM_MSP - bool "DaVinci DM355 EVM microcontroller" - depends on I2C=y && MACH_DAVINCI_DM355_EVM +config MFD_STMPE + bool "STMicroelectronics STMPE" + depends on (I2C=y || SPI_MASTER=y) && GENERIC_HARDIRQS + select MFD_CORE 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. + Support for the STMPE family of I/O Expanders from + STMicroelectronics. -config MFD_TI_SSP - tristate "TI Sequencer Serial Port support" - depends on ARCH_DAVINCI_TNETV107X && GENERIC_HARDIRQS + Currently supported devices are: + + STMPE811: GPIO, Touchscreen + STMPE1601: GPIO, Keypad + STMPE1801: GPIO, Keypad + STMPE2401: GPIO, Keypad + STMPE2403: GPIO, Keypad + + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the functionality + of the device. Currently available sub drivers are: + + GPIO: stmpe-gpio + Keypad: stmpe-keypad + Touchscreen: stmpe-ts + +menu "STMicroelectronics STMPE Interface Drivers" +depends on MFD_STMPE + +config STMPE_I2C + bool "STMicroelectronics STMPE I2C Inteface" + depends on I2C=y + default y + help + This is used to enable I2C interface of STMPE + +config STMPE_SPI + bool "STMicroelectronics STMPE SPI Inteface" + depends on SPI_MASTER + help + This is used to enable SPI interface of STMPE +endmenu + +config MFD_STA2X11 + bool "STMicroelectronics STA2X11" + depends on STA2X11 && GENERIC_HARDIRQS select MFD_CORE - ---help--- - Say Y here if you want support for the Sequencer Serial Port - in a Texas Instruments TNETV107X SoC. + select REGMAP_MMIO - To compile this driver as a module, choose M here: the - module will be called ti-ssp. +config MFD_SYSCON + bool "System Controller Register R/W Based on Regmap" + select REGMAP_MMIO + help + Select this option to enable accessing system control registers + via regmap. + +config MFD_DAVINCI_VOICECODEC + tristate + select MFD_CORE config MFD_TI_AM335X_TSCADC tristate "TI ADC / Touch Screen chip support" @@ -116,60 +672,56 @@ config MFD_TI_AM335X_TSCADC To compile this driver as a module, choose M here: the module will be called ti_am335x_tscadc. -config HTC_EGPIO - bool "HTC EGPIO support" - depends on GENERIC_HARDIRQS && GPIOLIB && ARM +config MFD_DM355EVM_MSP + bool "TI DaVinci DM355 EVM microcontroller" + depends on I2C=y && MACH_DAVINCI_DM355_EVM help - This driver supports the CPLD egpio chip present on - several HTC phones. It provides basic support for input - pins, output pins, and irqs. + 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_PASIC3 - tristate "HTC PASIC3 LED/DS1WM chip support" +config MFD_LP8788 + bool "TI LP8788 Power Management Unit Driver" + depends on I2C=y && GENERIC_HARDIRQS select MFD_CORE - depends on GENERIC_HARDIRQS - 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 HTC_I2CPLD - bool "HTC I2C PLD chip support" - depends on I2C=y && GPIOLIB + select REGMAP_I2C + select IRQ_DOMAIN help - If you say yes here you get support for the supposed CPLD - found on omap850 HTC devices like the HTC Wizard and HTC Herald. - This device provides input and output GPIOs through an I2C - interface to one or more sub-chips. + TI LP8788 PMU supports regulators, battery charger, RTC, + ADC, backlight driver and current sinks. -config UCB1400_CORE - tristate "Philips UCB1400 Core driver" - depends on AC97_BUS - depends on GPIOLIB +config MFD_OMAP_USB_HOST + bool "TI OMAP USBHS core and TLL driver" + depends on USB_EHCI_HCD_OMAP || USB_OHCI_HCD_OMAP3 + default y 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. + This is the core driver for the OAMP EHCI and OHCI drivers. + This MFD driver does the required setup functionalities for + OMAP USB Host drivers. -config MFD_LM3533 - tristate "LM3533 Lighting Power chip" - depends on I2C +config MFD_PALMAS + bool "TI Palmas series chips" select MFD_CORE select REGMAP_I2C - depends on GENERIC_HARDIRQS + select REGMAP_IRQ + depends on I2C=y && GENERIC_HARDIRQS help - Say yes here to enable support for National Semiconductor / TI - LM3533 Lighting Power chips. + If you say yes here you get support for the Palmas + series of PMIC chips from Texas Instruments. - This driver provides common support for accessing the device; - additional drivers must be enabled in order to use the LED, - backlight or ambient-light-sensor functionality of the device. +config MFD_TI_SSP + tristate "TI Sequencer Serial Port support" + depends on ARCH_DAVINCI_TNETV107X && GENERIC_HARDIRQS + select MFD_CORE + ---help--- + Say Y here if you want support for the Sequencer Serial Port + in a Texas Instruments TNETV107X SoC. + + To compile this driver as a module, choose M here: the + module will be called ti-ssp. config TPS6105X - tristate "TPS61050/61052 Boost Converters" + tristate "TI TPS61050/61052 Boost Converters" depends on I2C select REGULATOR select MFD_CORE @@ -182,7 +734,7 @@ config TPS6105X also contains a GPIO pin. config TPS65010 - tristate "TPS6501x Power Management chips" + tristate "TI TPS6501x Power Management chips" depends on I2C && GPIOLIB default y if MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_OSK help @@ -195,7 +747,7 @@ config TPS65010 will be called tps65010. config TPS6507X - tristate "TPS6507x Power Management / Touch Screen chips" + tristate "TI TPS6507x Power Management / Touch Screen chips" select MFD_CORE depends on I2C && GENERIC_HARDIRQS help @@ -206,8 +758,24 @@ config TPS6507X This driver can also be built as a module. If so, the module will be called tps6507x. +config TPS65911_COMPARATOR + tristate + +config MFD_TPS65090 + bool "TI TPS65090 Power Management chips" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + If you say yes here you get support for the TPS65090 series of + Power Management chips. + 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_TPS65217 - tristate "TPS65217 Power Management / White LED chips" + tristate "TI TPS65217 Power Management / White LED chips" depends on I2C && GENERIC_HARDIRQS select MFD_CORE select REGMAP_I2C @@ -222,7 +790,7 @@ config MFD_TPS65217 will be called tps65217. config MFD_TPS6586X - bool "TPS6586x Power Management chips" + bool "TI TPS6586x Power Management chips" depends on I2C=y && GENERIC_HARDIRQS select MFD_CORE select REGMAP_I2C @@ -237,7 +805,7 @@ config MFD_TPS6586X will be called tps6586x. config MFD_TPS65910 - bool "TPS65910 Power Management chip" + bool "TI TPS65910 Power Management chip" depends on I2C=y && GPIOLIB && GENERIC_HARDIRQS select MFD_CORE select REGMAP_I2C @@ -248,11 +816,14 @@ config MFD_TPS65910 Power Management chips. config MFD_TPS65912 - bool + bool "TI TPS65912 Power Management chip" depends on GPIOLIB + help + If you say yes here you get support for the TPS65912 series of + PM chips. config MFD_TPS65912_I2C - bool "TPS65912 Power Management chip with I2C" + bool "TI TPS65912 Power Management chip with I2C" select MFD_CORE select MFD_TPS65912 depends on I2C=y && GPIOLIB && GENERIC_HARDIRQS @@ -261,7 +832,7 @@ config MFD_TPS65912_I2C PM chips with I2C interface. config MFD_TPS65912_SPI - bool "TPS65912 Power Management chip with SPI" + bool "TI TPS65912 Power Management chip with SPI" select MFD_CORE select MFD_TPS65912 depends on SPI_MASTER && GPIOLIB && GENERIC_HARDIRQS @@ -283,18 +854,8 @@ config MFD_TPS80031 ADC, RTC, 2 PWM, System Voltage Regulator/Battery Charger with Power Path from USB, 32K clock generator. -config MENELAUS - bool "Texas Instruments TWL92330/Menelaus PM chip" - depends on I2C=y && ARCH_OMAP2 - 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" + bool "TI TWL4030/TWL5030/TWL6030/TPS659x0 Support" depends on I2C=y && GENERIC_HARDIRQS select IRQ_DOMAIN select REGMAP_I2C @@ -310,7 +871,7 @@ config TWL4030_CORE versions) and many other features. config TWL4030_MADC - tristate "Texas Instruments TWL4030 MADC" + tristate "TI TWL4030 MADC" depends on TWL4030_CORE help This driver provides support for triton TWL4030-MADC. The @@ -320,7 +881,7 @@ config TWL4030_MADC named twl4030-madc config TWL4030_POWER - bool "Support power resources on TWL4030 family chips" + bool "TI TWL4030 power resources" depends on TWL4030_CORE && ARM help Say yes here if you want to use the power resources on the @@ -333,13 +894,13 @@ config TWL4030_POWER or reset when a sleep, wakeup or warm reset event occurs. config MFD_TWL4030_AUDIO - bool + bool "TI TWL4030 Audio" depends on TWL4030_CORE && GENERIC_HARDIRQS select MFD_CORE default n config TWL6040_CORE - bool "Support for TWL6040 audio codec" + bool "TI TWL6040 audio codec" depends on I2C=y && GENERIC_HARDIRQS select MFD_CORE select REGMAP_I2C @@ -352,48 +913,53 @@ config TWL6040_CORE additional drivers must be enabled in order to use the functionality of the device (audio, vibra). -config MFD_STMPE - bool "Support STMicroelectronics STMPE" - depends on (I2C=y || SPI_MASTER=y) && GENERIC_HARDIRQS - select MFD_CORE +config MENELAUS + bool "TI TWL92330/Menelaus PM chip" + depends on I2C=y && ARCH_OMAP2 help - Support for the STMPE family of I/O Expanders from - STMicroelectronics. - - Currently supported devices are: - - STMPE811: GPIO, Touchscreen - STMPE1601: GPIO, Keypad - STMPE2401: GPIO, Keypad - STMPE2403: GPIO, Keypad + 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. - This driver provides common support for accessing the device, - additional drivers must be enabled in order to use the functionality - of the device. Currently available sub drivers are: +config MFD_WL1273_CORE + tristate "TI WL1273 FM radio" + depends on I2C && GENERIC_HARDIRQS + select MFD_CORE + default n + help + This is the core driver for the TI WL1273 FM radio. This MFD + driver connects the radio-wl1273 V4L2 module and the wl1273 + audio codec. - GPIO: stmpe-gpio - Keypad: stmpe-keypad - Touchscreen: stmpe-ts +config MFD_LM3533 + tristate "TI/National Semiconductor LM3533 Lighting Power chip" + depends on I2C + select MFD_CORE + select REGMAP_I2C + depends on GENERIC_HARDIRQS + help + Say yes here to enable support for National Semiconductor / TI + LM3533 Lighting Power chips. -menu "STMPE Interface Drivers" -depends on MFD_STMPE + This driver provides common support for accessing the device; + additional drivers must be enabled in order to use the LED, + backlight or ambient-light-sensor functionality of the device. -config STMPE_I2C - bool "STMPE I2C Inteface" - depends on I2C=y - default y - help - This is used to enable I2C interface of STMPE +config MFD_TIMBERDALE + tristate "Timberdale FPGA" + select MFD_CORE + depends on PCI && GPIOLIB + ---help--- + This is the core driver for the timberdale FPGA. This device is a + multifunction device which exposes numerous platform devices. -config STMPE_SPI - bool "STMPE SPI Inteface" - depends on SPI_MASTER - help - This is used to enable SPI interface of STMPE -endmenu + The timberdale FPGA can be found on the Intel Atom development board + for in-vehicle infontainment, called Russellville. config MFD_TC3589X - bool "Support Toshiba TC35892 and variants" + bool "Toshiba TC35892 and variants" depends on I2C=y && GENERIC_HARDIRQS select MFD_CORE help @@ -408,27 +974,15 @@ config MFD_TMIO default n config MFD_T7L66XB - bool "Support Toshiba T7L66XB" + bool "Toshiba T7L66XB" depends on ARM && HAVE_CLK && GENERIC_HARDIRQS select MFD_CORE select MFD_TMIO help Support for Toshiba Mobile IO Controller T7L66XB -config MFD_SMSC - bool "Support for the SMSC ECE1099 series chips" - depends on I2C=y && GENERIC_HARDIRQS - select MFD_CORE - select REGMAP_I2C - help - If you say yes here you get support for the - ece1099 chips from SMSC. - - To compile this driver as a module, choose M here: the - module will be called smsc. - config MFD_TC6387XB - bool "Support Toshiba TC6387XB" + bool "Toshiba TC6387XB" depends on ARM && HAVE_CLK select MFD_CORE select MFD_TMIO @@ -436,7 +990,7 @@ config MFD_TC6387XB Support for Toshiba Mobile IO Controller TC6387XB config MFD_TC6393XB - bool "Support Toshiba TC6393XB" + bool "Toshiba TC6393XB" depends on ARM && HAVE_CLK select GPIOLIB select MFD_CORE @@ -444,165 +998,14 @@ config MFD_TC6393XB 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_DA9052 - bool - select MFD_CORE - -config MFD_DA9052_SPI - bool "Support Dialog Semiconductor DA9052/53 PMIC variants with SPI" - select REGMAP_SPI - select REGMAP_IRQ - select PMIC_DA9052 - depends on SPI_MASTER=y && GENERIC_HARDIRQS - help - Support for the Dialog Semiconductor DA9052 PMIC - when controlled using SPI. 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_DA9052_I2C - bool "Support Dialog Semiconductor DA9052/53 PMIC variants with I2C" - select REGMAP_I2C - select REGMAP_IRQ - select PMIC_DA9052 - depends on I2C=y && GENERIC_HARDIRQS - help - Support for the Dialog Semiconductor DA9052 PMIC - when controlled using I2C. 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_DA9055 - bool "Dialog Semiconductor DA9055 PMIC Support" - select REGMAP_I2C - select REGMAP_IRQ - select PMIC_DA9055 - select MFD_CORE - depends on I2C=y && GENERIC_HARDIRQS - help - Say yes here for support of Dialog Semiconductor DA9055. This is - a Power Management IC. This driver provides common support for - accessing the device as well as the I2C interface to the chip itself. - Additional drivers must be enabled in order to use the functionality - of the device. - - This driver can be built as a module. If built as a module it will be - called "da9055" - -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_LP8788 - bool "Texas Instruments LP8788 Power Management Unit Driver" - depends on I2C=y && GENERIC_HARDIRQS - select MFD_CORE - select REGMAP_I2C - select IRQ_DOMAIN - help - TI LP8788 PMU supports regulators, battery charger, RTC, - ADC, backlight driver and current sinks. - -config MFD_MAX77686 - bool "Maxim Semiconductor MAX77686 PMIC Support" - depends on I2C=y && GENERIC_HARDIRQS - select MFD_CORE - select REGMAP_I2C - select IRQ_DOMAIN - help - Say yes here to support for Maxim Semiconductor MAX77686. - This is a Power Management IC with RTC on chip. - 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_MAX77693 - bool "Maxim Semiconductor MAX77693 PMIC Support" - depends on I2C=y && GENERIC_HARDIRQS - select MFD_CORE - select REGMAP_I2C - help - Say yes here to support for Maxim Semiconductor MAX77693. - This is a companion Power Management IC with Flash, Haptic, Charger, - and MUIC(Micro USB Interface Controller) controls on chip. - 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_MAX8907 - tristate "Maxim Semiconductor MAX8907 PMIC Support" - select MFD_CORE - depends on I2C=y && GENERIC_HARDIRQS - select REGMAP_I2C - select REGMAP_IRQ - help - Say yes here to support for Maxim Semiconductor MAX8907. This is - a Power Management IC. 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_MAX8925 - bool "Maxim Semiconductor MAX8925 PMIC Support" - depends on I2C=y && GENERIC_HARDIRQS - select MFD_CORE - help - Say yes here to support for Maxim Semiconductor MAX8925. This is - a Power Management IC. 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_MAX8997 - bool "Maxim Semiconductor MAX8997/8966 PMIC Support" - depends on I2C=y && GENERIC_HARDIRQS - select MFD_CORE - select IRQ_DOMAIN - help - Say yes here to support for Maxim Semiconductor MAX8997/8966. - This is a Power Management IC with RTC, Flash, Fuel Gauge, Haptic, - MUIC controls on chip. - 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_MAX8998 - bool "Maxim Semiconductor MAX8998/National LP3974 PMIC Support" - depends on I2C=y && GENERIC_HARDIRQS - select MFD_CORE - help - Say yes here to support for Maxim Semiconductor MAX8998 and - National Semiconductor LP3974. This is a Power Management IC. - 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_SEC_CORE - bool "SAMSUNG Electronics PMIC Series Support" - depends on I2C=y && GENERIC_HARDIRQS +config MFD_VX855 + tristate "VIA VX855/VX875 integrated south bridge" + depends on PCI && GENERIC_HARDIRQS select MFD_CORE - select REGMAP_I2C - select REGMAP_IRQ help - Support for the Samsung Electronics MFD series. - This driver provides common support for accessing the device, - additional drivers must be enabled in order to use the functionality - of the device + Say yes here to enable support for various functions of the + VIA VX855/VX875 south bridge. You will need to enable the vx855_spi + and/or vx855_gpio drivers for this to do anything useful. config MFD_ARIZONA select REGMAP @@ -611,7 +1014,7 @@ config MFD_ARIZONA bool config MFD_ARIZONA_I2C - tristate "Support Wolfson Microelectronics Arizona platform with I2C" + tristate "Wolfson Microelectronics Arizona platform with I2C" select MFD_ARIZONA select MFD_CORE select REGMAP_I2C @@ -621,7 +1024,7 @@ config MFD_ARIZONA_I2C core functionality controlled via I2C. config MFD_ARIZONA_SPI - tristate "Support Wolfson Microelectronics Arizona platform with SPI" + tristate "Wolfson Microelectronics Arizona platform with SPI" select MFD_ARIZONA select MFD_CORE select REGMAP_SPI @@ -631,19 +1034,19 @@ config MFD_ARIZONA_SPI core functionality controlled via I2C. config MFD_WM5102 - bool "Support Wolfson Microelectronics WM5102" + bool "Wolfson Microelectronics WM5102" depends on MFD_ARIZONA help Support for Wolfson Microelectronics WM5102 low power audio SoC config MFD_WM5110 - bool "Support Wolfson Microelectronics WM5110" + bool "Wolfson Microelectronics WM5110" depends on MFD_ARIZONA help Support for Wolfson Microelectronics WM5110 low power audio SoC config MFD_WM8400 - bool "Support Wolfson Microelectronics WM8400" + bool "Wolfson Microelectronics WM8400" select MFD_CORE depends on I2C=y && GENERIC_HARDIRQS select REGMAP_I2C @@ -658,7 +1061,7 @@ config MFD_WM831X depends on GENERIC_HARDIRQS config MFD_WM831X_I2C - bool "Support Wolfson Microelectronics WM831x/2x PMICs with I2C" + bool "Wolfson Microelectronics WM831x/2x PMICs with I2C" select MFD_CORE select MFD_WM831X select REGMAP_I2C @@ -671,7 +1074,7 @@ config MFD_WM831X_I2C order to use the functionality of the device. config MFD_WM831X_SPI - bool "Support Wolfson Microelectronics WM831x/2x PMICs with SPI" + bool "Wolfson Microelectronics WM831x/2x PMICs with SPI" select MFD_CORE select MFD_WM831X select REGMAP_SPI @@ -687,56 +1090,8 @@ config MFD_WM8350 bool depends on GENERIC_HARDIRQS -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 - bool "Support Wolfson Microelectronics WM8350 with I2C" + bool "Wolfson Microelectronics WM8350 with I2C" select MFD_WM8350 depends on I2C=y && GENERIC_HARDIRQS help @@ -747,7 +1102,7 @@ config MFD_WM8350_I2C selected to enable support for the functionality of the chip. config MFD_WM8994 - bool "Support Wolfson Microelectronics WM8994" + bool "Wolfson Microelectronics WM8994" select MFD_CORE select REGMAP_I2C select REGMAP_IRQ @@ -760,365 +1115,6 @@ config MFD_WM8994 core support for the WM8994, in order to use the actual functionaltiy of the device other drivers must be enabled. -config MFD_PCF50633 - tristate "Support for NXP PCF50633" - depends on I2C - select REGMAP_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 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 MFD_MC13783 - tristate - -config MFD_MC13XXX - tristate - depends on (SPI_MASTER || I2C) && GENERIC_HARDIRQS - select MFD_CORE - select MFD_MC13783 - help - Enable support for the Freescale MC13783 and MC13892 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_MC13XXX_SPI - tristate "Freescale MC13783 and MC13892 SPI interface" - depends on SPI_MASTER && GENERIC_HARDIRQS - select REGMAP_SPI - select MFD_MC13XXX - help - Select this if your MC13xxx is connected via an SPI bus. - -config MFD_MC13XXX_I2C - tristate "Freescale MC13892 I2C interface" - depends on I2C && GENERIC_HARDIRQS - select REGMAP_I2C - select MFD_MC13XXX - help - Select this if your MC13xxx is connected via an I2C bus. - -config ABX500_CORE - bool "ST-Ericsson ABX500 Mixed Signal Circuit register functions" - default y if ARCH_U300 || ARCH_U8500 - help - Say yes here if you have the ABX500 Mixed Signal IC family - chips. This core driver expose register access functions. - Functionality specific drivers using these functions can - remain unchanged when IC changes. Binding of the functions to - actual register access is done by the IC core driver. - -config AB3100_CORE - bool "ST-Ericsson AB3100 Mixed Signal Circuit core functions" - depends on I2C=y && ABX500_CORE && GENERIC_HARDIRQS - select MFD_CORE - 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 AB8500_CORE - bool "ST-Ericsson AB8500 Mixed Signal Power Management chip" - depends on GENERIC_HARDIRQS && ABX500_CORE && MFD_DB8500_PRCMU - select POWER_SUPPLY - select MFD_CORE - select IRQ_DOMAIN - help - Select this option to enable access to AB8500 power management - chip. This connects to U8500 either on the SSP/SPI bus (deprecated - since hardware version v1.0) or the I2C bus via PRCMU. It also adds - the irq_chip parts for handling the Mixed Signal chip events. - This chip embeds various other multimedia funtionalities as well. - -config AB8500_DEBUG - bool "Enable debug info via debugfs" - depends on AB8500_CORE && DEBUG_FS - default y if DEBUG_FS - help - Select this option if you want debug information using the debug - filesystem, debugfs. - -config AB8500_GPADC - bool "AB8500 GPADC driver" - depends on AB8500_CORE && REGULATOR_AB8500 - default y - help - AB8500 GPADC driver used to convert Acc and battery/ac/usb voltage - -config MFD_DB8500_PRCMU - bool "ST-Ericsson DB8500 Power Reset Control Management Unit" - depends on UX500_SOC_DB8500 - select MFD_CORE - help - Select this option to enable support for the DB8500 Power Reset - and Control Management Unit. This is basically an autonomous - system controller running an XP70 microprocessor, which is accessed - through a register map. - -config MFD_CS5535 - tristate "Support for CS5535 and CS5536 southbridge core functions" - select MFD_CORE - depends on PCI && X86 - ---help--- - This is the core driver for CS5535/CS5536 MFD functions. This is - necessary for using the board's GPIO and MFGPT functionality. - -config MFD_TIMBERDALE - tristate "Support for the Timberdale FPGA" - select MFD_CORE - depends on PCI && GPIOLIB - ---help--- - This is the core driver for the timberdale FPGA. This device is a - multifunction device which exposes numerous platform devices. - - The timberdale FPGA can be found on the Intel Atom development board - for in-vehicle infontainment, called Russellville. - -config LPC_SCH - tristate "Intel SCH LPC" - depends on PCI && GENERIC_HARDIRQS - select MFD_CORE - help - LPC bridge function of the Intel SCH provides support for - System Management Bus and General Purpose I/O. - -config LPC_ICH - tristate "Intel ICH LPC" - depends on PCI && GENERIC_HARDIRQS - select MFD_CORE - help - The LPC bridge function of the Intel ICH provides support for - many functional units. This driver provides needed support for - other drivers to control these functions, currently GPIO and - watchdog. - -config MFD_RDC321X - tristate "Support for RDC-R321x southbridge" - select MFD_CORE - depends on PCI && GENERIC_HARDIRQS - help - Say yes here if you want to have support for the RDC R-321x SoC - southbridge which provides access to GPIOs and Watchdog using the - southbridge PCI device configuration space. - -config MFD_JANZ_CMODIO - tristate "Support for Janz CMOD-IO PCI MODULbus Carrier Board" - select MFD_CORE - depends on PCI && GENERIC_HARDIRQS - help - This is the core driver for the Janz CMOD-IO PCI MODULbus - carrier board. This device is a PCI to MODULbus bridge which may - host many different types of MODULbus daughterboards, including - CAN and GPIO controllers. - -config MFD_JZ4740_ADC - bool "Support for the JZ4740 SoC ADC core" - select MFD_CORE - select GENERIC_IRQ_CHIP - depends on MACH_JZ4740 - help - Say yes here if you want support for the ADC unit in the JZ4740 SoC. - This driver is necessary for jz4740-battery and jz4740-hwmon driver. - -config MFD_VX855 - tristate "Support for VIA VX855/VX875 integrated south bridge" - depends on PCI && GENERIC_HARDIRQS - select MFD_CORE - help - Say yes here to enable support for various functions of the - VIA VX855/VX875 south bridge. You will need to enable the vx855_spi - and/or vx855_gpio drivers for this to do anything useful. - -config MFD_WL1273_CORE - tristate "Support for TI WL1273 FM radio." - depends on I2C && GENERIC_HARDIRQS - select MFD_CORE - default n - help - This is the core driver for the TI WL1273 FM radio. This MFD - driver connects the radio-wl1273 V4L2 module and the wl1273 - audio codec. - -config MFD_OMAP_USB_HOST - bool "Support OMAP USBHS core and TLL driver" - depends on USB_EHCI_HCD_OMAP || USB_OHCI_HCD_OMAP3 - default y - help - This is the core driver for the OAMP EHCI and OHCI drivers. - This MFD driver does the required setup functionalities for - OMAP USB Host drivers. - -config MFD_PM8XXX - tristate - -config MFD_PM8921_CORE - tristate "Qualcomm PM8921 PMIC chip" - depends on MSM_SSBI - select MFD_CORE - select MFD_PM8XXX - help - If you say yes to this option, support will be included for the - built-in PM8921 PMIC chip. - - This is required if your board has a PM8921 and uses its features, - such as: MPPs, GPIOs, regulators, interrupts, and PWM. - - Say M here if you want to include support for PM8921 chip as a module. - This will build a module called "pm8921-core". - -config MFD_PM8XXX_IRQ - bool "Support for Qualcomm PM8xxx IRQ features" - depends on MFD_PM8XXX - default y if MFD_PM8XXX - help - This is the IRQ driver for Qualcomm PM 8xxx PMIC chips. - - This is required to use certain other PM 8xxx features, such as GPIO - and MPP. - -config TPS65911_COMPARATOR - tristate - -config MFD_TPS65090 - bool "TPS65090 Power Management chips" - depends on I2C=y && GENERIC_HARDIRQS - select MFD_CORE - select REGMAP_I2C - select REGMAP_IRQ - help - If you say yes here you get support for the TPS65090 series of - Power Management chips. - 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_AAT2870_CORE - bool "Support for the AnalogicTech AAT2870" - select MFD_CORE - depends on I2C=y && GPIOLIB && GENERIC_HARDIRQS - help - If you say yes here you get support for the AAT2870. - 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_INTEL_MSIC - bool "Support for Intel MSIC" - depends on INTEL_SCU_IPC - select MFD_CORE - help - Select this option to enable access to Intel MSIC (Avatele - Passage) chip. This chip embeds audio, battery, GPIO, etc. - devices used in Intel Medfield platforms. - -config MFD_RC5T583 - bool "Ricoh RC5T583 Power Management system device" - depends on I2C=y && GENERIC_HARDIRQS - select MFD_CORE - select REGMAP_I2C - help - Select this option to get support for the RICOH583 Power - Management system device. - This driver provides common support for accessing the device - through i2c interface. The device supports multiple sub-devices - like GPIO, interrupts, RTC, LDO and DCDC regulators, onkey. - Additional drivers must be enabled in order to use the - different functionality of the device. - -config MFD_STA2X11 - bool "STA2X11 multi function device support" - depends on STA2X11 && GENERIC_HARDIRQS - select MFD_CORE - select REGMAP_MMIO - -config MFD_SYSCON - bool "System Controller Register R/W Based on Regmap" - depends on OF - select REGMAP_MMIO - help - Select this option to enable accessing system control registers - via regmap. - -config MFD_PALMAS - bool "Support for the TI Palmas series chips" - select MFD_CORE - select REGMAP_I2C - select REGMAP_IRQ - depends on I2C=y && GENERIC_HARDIRQS - help - If you say yes here you get support for the Palmas - series of PMIC chips from Texas Instruments. - -config MFD_VIPERBOARD - tristate "Support for Nano River Technologies Viperboard" - select MFD_CORE - depends on USB && GENERIC_HARDIRQS - default n - help - Say yes here if you want support for Nano River Technologies - Viperboard. - There are mfd cell drivers available for i2c master, adc and - both gpios found on the board. The spi part does not yet - have a driver. - You need to select the mfd cell drivers separately. - The drivers do not support all features the board exposes. - -config MFD_RETU - tristate "Support for Retu multi-function device" - select MFD_CORE - depends on I2C && GENERIC_HARDIRQS - select REGMAP_IRQ - help - Retu is a multi-function device found on Nokia Internet Tablets - (770, N800 and N810). - -config MFD_AS3711 - bool "Support for AS3711" - select MFD_CORE - select REGMAP_I2C - select REGMAP_IRQ - depends on I2C=y && GENERIC_HARDIRQS - help - Support for the AS3711 PMIC from AMS - endmenu endif diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index b90409c23664..718e94a2a9a7 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -8,8 +8,11 @@ obj-$(CONFIG_MFD_88PM800) += 88pm800.o 88pm80x.o obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o obj-$(CONFIG_MFD_SM501) += sm501.o obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o +obj-$(CONFIG_MFD_CROS_EC) += cros_ec.o +obj-$(CONFIG_MFD_CROS_EC_I2C) += cros_ec_i2c.o +obj-$(CONFIG_MFD_CROS_EC_SPI) += cros_ec_spi.o -rtsx_pci-objs := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o +rtsx_pci-objs := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o rts5249.o obj-$(CONFIG_MFD_RTSX_PCI) += rtsx_pci.o obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o @@ -131,6 +134,10 @@ obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o obj-$(CONFIG_MFD_TPS6586X) += tps6586x.o obj-$(CONFIG_MFD_VX855) += vx855.o obj-$(CONFIG_MFD_WL1273_CORE) += wl1273-core.o + +si476x-core-y := si476x-cmd.o si476x-prop.o si476x-i2c.o +obj-$(CONFIG_MFD_SI476X_CORE) += si476x-core.o + obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o diff --git a/drivers/mfd/aat2870-core.c b/drivers/mfd/aat2870-core.c index f1beb4971f87..dfdb0a2b6835 100644 --- a/drivers/mfd/aat2870-core.c +++ b/drivers/mfd/aat2870-core.c @@ -367,12 +367,12 @@ static int aat2870_i2c_probe(struct i2c_client *client, int i, j; int ret = 0; - aat2870 = kzalloc(sizeof(struct aat2870_data), GFP_KERNEL); + aat2870 = devm_kzalloc(&client->dev, sizeof(struct aat2870_data), + GFP_KERNEL); if (!aat2870) { dev_err(&client->dev, "Failed to allocate memory for aat2870\n"); - ret = -ENOMEM; - goto out; + return -ENOMEM; } aat2870->dev = &client->dev; @@ -400,12 +400,12 @@ static int aat2870_i2c_probe(struct i2c_client *client, aat2870->init(aat2870); if (aat2870->en_pin >= 0) { - ret = gpio_request_one(aat2870->en_pin, GPIOF_OUT_INIT_HIGH, - "aat2870-en"); + ret = devm_gpio_request_one(&client->dev, aat2870->en_pin, + GPIOF_OUT_INIT_HIGH, "aat2870-en"); if (ret < 0) { dev_err(&client->dev, "Failed to request GPIO %d\n", aat2870->en_pin); - goto out_kfree; + return ret; } } @@ -436,11 +436,6 @@ static int aat2870_i2c_probe(struct i2c_client *client, out_disable: aat2870_disable(aat2870); - if (aat2870->en_pin >= 0) - gpio_free(aat2870->en_pin); -out_kfree: - kfree(aat2870); -out: return ret; } @@ -452,11 +447,8 @@ static int aat2870_i2c_remove(struct i2c_client *client) mfd_remove_devices(aat2870->dev); aat2870_disable(aat2870); - if (aat2870->en_pin >= 0) - gpio_free(aat2870->en_pin); if (aat2870->uninit) aat2870->uninit(aat2870); - kfree(aat2870); return 0; } diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index 2ec7725f4a08..a9bb140bc86b 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -753,6 +753,7 @@ static struct mfd_cell ab3100_devs[] = { }, { .name = "ab3100-regulators", + .of_compatible = "stericsson,ab3100-regulators", .id = -1, }, { diff --git a/drivers/mfd/ab3100-otp.c b/drivers/mfd/ab3100-otp.c index 8440010eb2b8..d7ce016029fa 100644 --- a/drivers/mfd/ab3100-otp.c +++ b/drivers/mfd/ab3100-otp.c @@ -248,19 +248,7 @@ static struct platform_driver ab3100_otp_driver = { .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_platform_driver_probe(ab3100_otp_driver, ab3100_otp_probe); MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); MODULE_DESCRIPTION("AB3100 OTP Readout Driver"); diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index 7c84ced2e01b..8e8a016effe9 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -95,6 +95,7 @@ #define AB8500_IT_MASK22_REG 0x55 #define AB8500_IT_MASK23_REG 0x56 #define AB8500_IT_MASK24_REG 0x57 +#define AB8500_IT_MASK25_REG 0x58 /* * latch hierarchy registers @@ -102,15 +103,25 @@ #define AB8500_IT_LATCHHIER1_REG 0x60 #define AB8500_IT_LATCHHIER2_REG 0x61 #define AB8500_IT_LATCHHIER3_REG 0x62 +#define AB8540_IT_LATCHHIER4_REG 0x63 #define AB8500_IT_LATCHHIER_NUM 3 +#define AB8540_IT_LATCHHIER_NUM 4 #define AB8500_REV_REG 0x80 #define AB8500_IC_NAME_REG 0x82 #define AB8500_SWITCH_OFF_STATUS 0x00 #define AB8500_TURN_ON_STATUS 0x00 +#define AB8505_TURN_ON_STATUS_2 0x04 +#define AB8500_CH_USBCH_STAT1_REG 0x02 +#define VBUS_DET_DBNC100 0x02 +#define VBUS_DET_DBNC1 0x01 + +static DEFINE_SPINLOCK(on_stat_lock); +static u8 turn_on_stat_mask = 0xFF; +static u8 turn_on_stat_set; static bool no_bm; /* No battery management */ module_param(no_bm, bool, S_IRUGO); @@ -130,9 +141,15 @@ static const int ab8500_irq_regoffset[AB8500_NUM_IRQ_REGS] = { 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 18, 19, 20, 21, }; -/* AB9540 support */ +/* AB9540 / AB8505 support */ static const int ab9540_irq_regoffset[AB9540_NUM_IRQ_REGS] = { - 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 18, 19, 20, 21, 12, 13, 24, + 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 18, 19, 20, 21, 12, 13, 24, 5, 22, 23 +}; + +/* AB8540 support */ +static const int ab8540_irq_regoffset[AB8540_NUM_IRQ_REGS] = { + 0, 1, 2, 3, 4, -1, -1, -1, -1, 11, 18, 19, 20, 21, 12, 13, 24, 5, 22, 23, + 25, 26, 27, 28, 29, 30, 31, }; static const char ab8500_version_str[][7] = { @@ -352,6 +369,9 @@ static void ab8500_irq_sync_unlock(struct irq_data *data) is_ab8500_1p1_or_earlier(ab8500)) continue; + if (ab8500->irq_reg_offset[i] < 0) + continue; + ab8500->oldmask[i] = new; reg = AB8500_IT_MASK1_REG + ab8500->irq_reg_offset[i]; @@ -423,25 +443,38 @@ static struct irq_chip ab8500_irq_chip = { .irq_set_type = ab8500_irq_set_type, }; +static void update_latch_offset(u8 *offset, int i) +{ + /* Fix inconsistent ITFromLatch25 bit mapping... */ + if (unlikely(*offset == 17)) + *offset = 24; + /* Fix inconsistent ab8540 bit mapping... */ + if (unlikely(*offset == 16)) + *offset = 25; + if ((i==3) && (*offset >= 24)) + *offset += 2; +} + static int ab8500_handle_hierarchical_line(struct ab8500 *ab8500, int latch_offset, u8 latch_val) { - int int_bit = __ffs(latch_val); - int line, i; + int int_bit, line, i; - do { - int_bit = __ffs(latch_val); + for (i = 0; i < ab8500->mask_size; i++) + if (ab8500->irq_reg_offset[i] == latch_offset) + break; - for (i = 0; i < ab8500->mask_size; i++) - if (ab8500->irq_reg_offset[i] == latch_offset) - break; + if (i >= ab8500->mask_size) { + dev_err(ab8500->dev, "Register offset 0x%2x not declared\n", + latch_offset); + return -ENXIO; + } - if (i >= ab8500->mask_size) { - dev_err(ab8500->dev, "Register offset 0x%2x not declared\n", - latch_offset); - return -ENXIO; - } + /* ignore masked out interrupts */ + latch_val &= ~ab8500->mask[i]; + while (latch_val) { + int_bit = __ffs(latch_val); line = (i << 3) + int_bit; latch_val &= ~(1 << int_bit); @@ -459,7 +492,7 @@ static int ab8500_handle_hierarchical_line(struct ab8500 *ab8500, line += 1; handle_nested_irq(ab8500->irq_base + line); - } while (latch_val); + } return 0; } @@ -474,9 +507,7 @@ static int ab8500_handle_hierarchical_latch(struct ab8500 *ab8500, latch_bit = __ffs(hier_val); latch_offset = (hier_offset << 3) + latch_bit; - /* Fix inconsistent ITFromLatch25 bit mapping... */ - if (unlikely(latch_offset == 17)) - latch_offset = 24; + update_latch_offset(&latch_offset, hier_offset); status = get_register_interruptible(ab8500, AB8500_INTERRUPT, @@ -504,7 +535,7 @@ static irqreturn_t ab8500_hierarchical_irq(int irq, void *dev) dev_vdbg(ab8500->dev, "interrupt\n"); /* Hierarchical interrupt version */ - for (i = 0; i < AB8500_IT_LATCHHIER_NUM; i++) { + for (i = 0; i < (ab8500->it_latchhier_num); i++) { int status; u8 hier_val; @@ -520,63 +551,6 @@ static irqreturn_t ab8500_hierarchical_irq(int irq, void *dev) return IRQ_HANDLED; } -/** - * ab8500_irq_get_virq(): Map an interrupt on a chip to a virtual IRQ - * - * @ab8500: ab8500_irq controller to operate on. - * @irq: index of the interrupt requested in the chip IRQs - * - * Useful for drivers to request their own IRQs. - */ -static int ab8500_irq_get_virq(struct ab8500 *ab8500, int irq) -{ - if (!ab8500) - return -EINVAL; - - return irq_create_mapping(ab8500->domain, irq); -} - -static irqreturn_t ab8500_irq(int irq, void *dev) -{ - struct ab8500 *ab8500 = dev; - int i; - - dev_vdbg(ab8500->dev, "interrupt\n"); - - atomic_inc(&ab8500->transfer_ongoing); - - for (i = 0; i < ab8500->mask_size; i++) { - int regoffset = ab8500->irq_reg_offset[i]; - int status; - u8 value; - - /* - * Interrupt register 12 doesn't exist prior to AB8500 version - * 2.0 - */ - if (regoffset == 11 && is_ab8500_1p1_or_earlier(ab8500)) - continue; - - status = get_register_interruptible(ab8500, AB8500_INTERRUPT, - AB8500_IT_LATCH1_REG + regoffset, &value); - if (status < 0 || value == 0) - continue; - - do { - int bit = __ffs(value); - int line = i * 8 + bit; - int virq = ab8500_irq_get_virq(ab8500, line); - - handle_nested_irq(virq); - ab8500_debug_register_interrupt(line); - value &= ~(1 << bit); - - } while (value); - } - atomic_dec(&ab8500->transfer_ongoing); - return IRQ_HANDLED; -} - static int ab8500_irq_map(struct irq_domain *d, unsigned int virq, irq_hw_number_t hwirq) { @@ -607,7 +581,9 @@ static int ab8500_irq_init(struct ab8500 *ab8500, struct device_node *np) { int num_irqs; - if (is_ab9540(ab8500)) + if (is_ab8540(ab8500)) + num_irqs = AB8540_NR_IRQS; + else if (is_ab9540(ab8500)) num_irqs = AB9540_NR_IRQS; else if (is_ab8505(ab8500)) num_irqs = AB8505_NR_IRQS; @@ -650,6 +626,15 @@ static struct resource ab8500_gpadc_resources[] = { }, }; +static struct resource ab8505_gpadc_resources[] = { + { + .name = "SW_CONV_END", + .start = AB8500_INT_GP_SW_ADC_CONV_END, + .end = AB8500_INT_GP_SW_ADC_CONV_END, + .flags = IORESOURCE_IRQ, + }, +}; + static struct resource ab8500_rtc_resources[] = { { .name = "60S", @@ -973,6 +958,30 @@ static struct resource ab8505_iddet_resources[] = { .end = AB8505_INT_KEYSTUCK, .flags = IORESOURCE_IRQ, }, + { + .name = "VBUS_DET_R", + .start = AB8500_INT_VBUS_DET_R, + .end = AB8500_INT_VBUS_DET_R, + .flags = IORESOURCE_IRQ, + }, + { + .name = "VBUS_DET_F", + .start = AB8500_INT_VBUS_DET_F, + .end = AB8500_INT_VBUS_DET_F, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ID_DET_PLUGR", + .start = AB8500_INT_ID_DET_PLUGR, + .end = AB8500_INT_ID_DET_PLUGR, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ID_DET_PLUGF", + .start = AB8500_INT_ID_DET_PLUGF, + .end = AB8500_INT_ID_DET_PLUGF, + .flags = IORESOURCE_IRQ, + }, }; static struct resource ab8500_temp_resources[] = { @@ -984,7 +993,42 @@ static struct resource ab8500_temp_resources[] = { }, }; -static struct mfd_cell abx500_common_devs[] = { +static struct mfd_cell ab8500_bm_devs[] = { + { + .name = "ab8500-charger", + .of_compatible = "stericsson,ab8500-charger", + .num_resources = ARRAY_SIZE(ab8500_charger_resources), + .resources = ab8500_charger_resources, + .platform_data = &ab8500_bm_data, + .pdata_size = sizeof(ab8500_bm_data), + }, + { + .name = "ab8500-btemp", + .of_compatible = "stericsson,ab8500-btemp", + .num_resources = ARRAY_SIZE(ab8500_btemp_resources), + .resources = ab8500_btemp_resources, + .platform_data = &ab8500_bm_data, + .pdata_size = sizeof(ab8500_bm_data), + }, + { + .name = "ab8500-fg", + .of_compatible = "stericsson,ab8500-fg", + .num_resources = ARRAY_SIZE(ab8500_fg_resources), + .resources = ab8500_fg_resources, + .platform_data = &ab8500_bm_data, + .pdata_size = sizeof(ab8500_bm_data), + }, + { + .name = "ab8500-chargalg", + .of_compatible = "stericsson,ab8500-chargalg", + .num_resources = ARRAY_SIZE(ab8500_chargalg_resources), + .resources = ab8500_chargalg_resources, + .platform_data = &ab8500_bm_data, + .pdata_size = sizeof(ab8500_bm_data), + }, +}; + +static struct mfd_cell ab8500_devs[] = { #ifdef CONFIG_DEBUG_FS { .name = "ab8500-debug", @@ -1007,7 +1051,6 @@ static struct mfd_cell abx500_common_devs[] = { }, { .name = "ab8500-gpadc", - .of_compatible = "stericsson,ab8500-gpadc", .num_resources = ARRAY_SIZE(ab8500_gpadc_resources), .resources = ab8500_gpadc_resources, }, @@ -1024,6 +1067,7 @@ static struct mfd_cell abx500_common_devs[] = { .resources = ab8500_av_acc_detect_resources, }, { + .name = "ab8500-poweron-key", .of_compatible = "stericsson,ab8500-poweron-key", .num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources), @@ -1053,82 +1097,221 @@ static struct mfd_cell abx500_common_devs[] = { .of_compatible = "stericsson,ab8500-denc", }, { + .name = "ab8500-gpio", + .of_compatible = "stericsson,ab8500-gpio", + }, + { .name = "abx500-temp", .of_compatible = "stericsson,abx500-temp", .num_resources = ARRAY_SIZE(ab8500_temp_resources), .resources = ab8500_temp_resources, }, + { + .name = "ab8500-usb", + .of_compatible = "stericsson,ab8500-usb", + .num_resources = ARRAY_SIZE(ab8500_usb_resources), + .resources = ab8500_usb_resources, + }, + { + .name = "ab8500-codec", + }, }; -static struct mfd_cell ab8500_bm_devs[] = { +static struct mfd_cell ab9540_devs[] = { +#ifdef CONFIG_DEBUG_FS { - .name = "ab8500-charger", - .of_compatible = "stericsson,ab8500-charger", - .num_resources = ARRAY_SIZE(ab8500_charger_resources), - .resources = ab8500_charger_resources, - .platform_data = &ab8500_bm_data, - .pdata_size = sizeof(ab8500_bm_data), + .name = "ab8500-debug", + .num_resources = ARRAY_SIZE(ab8500_debug_resources), + .resources = ab8500_debug_resources, }, +#endif { - .name = "ab8500-btemp", - .of_compatible = "stericsson,ab8500-btemp", - .num_resources = ARRAY_SIZE(ab8500_btemp_resources), - .resources = ab8500_btemp_resources, - .platform_data = &ab8500_bm_data, - .pdata_size = sizeof(ab8500_bm_data), + .name = "ab8500-sysctrl", }, { - .name = "ab8500-fg", - .of_compatible = "stericsson,ab8500-fg", - .num_resources = ARRAY_SIZE(ab8500_fg_resources), - .resources = ab8500_fg_resources, - .platform_data = &ab8500_bm_data, - .pdata_size = sizeof(ab8500_bm_data), + .name = "ab8500-regulator", }, { - .name = "ab8500-chargalg", - .of_compatible = "stericsson,ab8500-chargalg", - .num_resources = ARRAY_SIZE(ab8500_chargalg_resources), - .resources = ab8500_chargalg_resources, - .platform_data = &ab8500_bm_data, - .pdata_size = sizeof(ab8500_bm_data), + .name = "abx500-clk", + .of_compatible = "stericsson,abx500-clk", + }, + { + .name = "ab8500-gpadc", + .of_compatible = "stericsson,ab8500-gpadc", + .num_resources = ARRAY_SIZE(ab8500_gpadc_resources), + .resources = ab8500_gpadc_resources, + }, + { + .name = "ab8500-rtc", + .num_resources = ARRAY_SIZE(ab8500_rtc_resources), + .resources = ab8500_rtc_resources, + }, + { + .name = "ab8500-acc-det", + .num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources), + .resources = ab8500_av_acc_detect_resources, + }, + { + .name = "ab8500-poweron-key", + .num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources), + .resources = ab8500_poweronkey_db_resources, + }, + { + .name = "ab8500-pwm", + .id = 1, + }, + { + .name = "ab8500-leds", + }, + { + .name = "abx500-temp", + .num_resources = ARRAY_SIZE(ab8500_temp_resources), + .resources = ab8500_temp_resources, + }, + { + .name = "pinctrl-ab9540", + .of_compatible = "stericsson,ab9540-gpio", + }, + { + .name = "ab9540-usb", + .num_resources = ARRAY_SIZE(ab8500_usb_resources), + .resources = ab8500_usb_resources, + }, + { + .name = "ab9540-codec", + }, + { + .name = "ab-iddet", + .num_resources = ARRAY_SIZE(ab8505_iddet_resources), + .resources = ab8505_iddet_resources, }, }; -static struct mfd_cell ab8500_devs[] = { +/* Device list for ab8505 */ +static struct mfd_cell ab8505_devs[] = { +#ifdef CONFIG_DEBUG_FS { - .name = "pinctrl-ab8500", - .of_compatible = "stericsson,ab8500-gpio", + .name = "ab8500-debug", + .num_resources = ARRAY_SIZE(ab8500_debug_resources), + .resources = ab8500_debug_resources, + }, +#endif + { + .name = "ab8500-sysctrl", + }, + { + .name = "ab8500-regulator", + }, + { + .name = "abx500-clk", + .of_compatible = "stericsson,abx500-clk", + }, + { + .name = "ab8500-gpadc", + .num_resources = ARRAY_SIZE(ab8505_gpadc_resources), + .resources = ab8505_gpadc_resources, + }, + { + .name = "ab8500-rtc", + .num_resources = ARRAY_SIZE(ab8500_rtc_resources), + .resources = ab8500_rtc_resources, + }, + { + .name = "ab8500-acc-det", + .num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources), + .resources = ab8500_av_acc_detect_resources, + }, + { + .name = "ab8500-poweron-key", + .num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources), + .resources = ab8500_poweronkey_db_resources, + }, + { + .name = "ab8500-pwm", + .id = 1, + }, + { + .name = "ab8500-leds", + }, + { + .name = "ab8500-gpio", }, { .name = "ab8500-usb", - .of_compatible = "stericsson,ab8500-usb", .num_resources = ARRAY_SIZE(ab8500_usb_resources), .resources = ab8500_usb_resources, }, { .name = "ab8500-codec", - .of_compatible = "stericsson,ab8500-codec", + }, + { + .name = "ab-iddet", + .num_resources = ARRAY_SIZE(ab8505_iddet_resources), + .resources = ab8505_iddet_resources, }, }; -static struct mfd_cell ab9540_devs[] = { +static struct mfd_cell ab8540_devs[] = { +#ifdef CONFIG_DEBUG_FS { - .name = "pinctrl-ab9540", - .of_compatible = "stericsson,ab9540-gpio", + .name = "ab8500-debug", + .num_resources = ARRAY_SIZE(ab8500_debug_resources), + .resources = ab8500_debug_resources, }, +#endif { - .name = "ab9540-usb", + .name = "ab8500-sysctrl", + }, + { + .name = "ab8500-regulator", + }, + { + .name = "abx500-clk", + .of_compatible = "stericsson,abx500-clk", + }, + { + .name = "ab8500-gpadc", + .num_resources = ARRAY_SIZE(ab8505_gpadc_resources), + .resources = ab8505_gpadc_resources, + }, + { + .name = "ab8500-rtc", + .num_resources = ARRAY_SIZE(ab8500_rtc_resources), + .resources = ab8500_rtc_resources, + }, + { + .name = "ab8500-acc-det", + .num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources), + .resources = ab8500_av_acc_detect_resources, + }, + { + .name = "ab8500-poweron-key", + .num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources), + .resources = ab8500_poweronkey_db_resources, + }, + { + .name = "ab8500-pwm", + .id = 1, + }, + { + .name = "ab8500-leds", + }, + { + .name = "abx500-temp", + .num_resources = ARRAY_SIZE(ab8500_temp_resources), + .resources = ab8500_temp_resources, + }, + { + .name = "ab8500-gpio", + }, + { + .name = "ab8540-usb", .num_resources = ARRAY_SIZE(ab8500_usb_resources), .resources = ab8500_usb_resources, }, { - .name = "ab9540-codec", + .name = "ab8540-codec", }, -}; - -/* Device list common to ab9540 and ab8505 */ -static struct mfd_cell ab9540_ab8505_devs[] = { { .name = "ab-iddet", .num_resources = ARRAY_SIZE(ab8505_iddet_resources), @@ -1142,6 +1325,7 @@ static ssize_t show_chip_id(struct device *dev, struct ab8500 *ab8500; ab8500 = dev_get_drvdata(dev); + return sprintf(buf, "%#x\n", ab8500 ? ab8500->chip_id : -EINVAL); } @@ -1171,6 +1355,15 @@ static ssize_t show_switch_off_status(struct device *dev, return sprintf(buf, "%#x\n", value); } +/* use mask and set to override the register turn_on_stat value */ +void ab8500_override_turn_on_stat(u8 mask, u8 set) +{ + spin_lock(&on_stat_lock); + turn_on_stat_mask = mask; + turn_on_stat_set = set; + spin_unlock(&on_stat_lock); +} + /* * ab8500 has turned on due to (TURN_ON_STATUS): * 0x01 PORnVbat @@ -1194,9 +1387,38 @@ static ssize_t show_turn_on_status(struct device *dev, AB8500_TURN_ON_STATUS, &value); if (ret < 0) return ret; + + /* + * In L9540, turn_on_status register is not updated correctly if + * the device is rebooted with AC/USB charger connected. Due to + * this, the device boots android instead of entering into charge + * only mode. Read the AC/USB status register to detect the charger + * presence and update the turn on status manually. + */ + if (is_ab9540(ab8500)) { + spin_lock(&on_stat_lock); + value = (value & turn_on_stat_mask) | turn_on_stat_set; + spin_unlock(&on_stat_lock); + } + return sprintf(buf, "%#x\n", value); } +static ssize_t show_turn_on_status_2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + u8 value; + struct ab8500 *ab8500; + + ab8500 = dev_get_drvdata(dev); + ret = get_register_interruptible(ab8500, AB8500_SYS_CTRL1_BLOCK, + AB8505_TURN_ON_STATUS_2, &value); + if (ret < 0) + return ret; + return sprintf(buf, "%#x\n", (value & 0x1)); +} + static ssize_t show_ab9540_dbbrstn(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1253,6 +1475,7 @@ exit: static DEVICE_ATTR(chip_id, S_IRUGO, show_chip_id, NULL); static DEVICE_ATTR(switch_off_status, S_IRUGO, show_switch_off_status, NULL); static DEVICE_ATTR(turn_on_status, S_IRUGO, show_turn_on_status, NULL); +static DEVICE_ATTR(turn_on_status_2, S_IRUGO, show_turn_on_status_2, NULL); static DEVICE_ATTR(dbbrstn, S_IRUGO | S_IWUSR, show_ab9540_dbbrstn, store_ab9540_dbbrstn); @@ -1263,6 +1486,11 @@ static struct attribute *ab8500_sysfs_entries[] = { NULL, }; +static struct attribute *ab8505_sysfs_entries[] = { + &dev_attr_turn_on_status_2.attr, + NULL, +}; + static struct attribute *ab9540_sysfs_entries[] = { &dev_attr_chip_id.attr, &dev_attr_switch_off_status.attr, @@ -1275,6 +1503,10 @@ static struct attribute_group ab8500_attr_group = { .attrs = ab8500_sysfs_entries, }; +static struct attribute_group ab8505_attr_group = { + .attrs = ab8505_sysfs_entries, +}; + static struct attribute_group ab9540_attr_group = { .attrs = ab9540_sysfs_entries, }; @@ -1290,6 +1522,15 @@ static int ab8500_probe(struct platform_device *pdev) "Battery level lower than power on reset threshold", "Power on key 1 pressed longer than 10 seconds", "DB8500 thermal shutdown"}; + static char *turn_on_status[] = { + "Battery rising (Vbat)", + "Power On Key 1 dbF", + "Power On Key 2 dbF", + "RTC Alarm", + "Main Charger Detect", + "Vbus Detect (USB)", + "USB ID Detect", + "UART Factory Mode Detect"}; struct ab8500_platform_data *plat = dev_get_platdata(&pdev->dev); const struct platform_device_id *platid = platform_get_device_id(pdev); enum ab8500_version version = AB8500_VERSION_UNDEFINED; @@ -1351,13 +1592,20 @@ static int ab8500_probe(struct platform_device *pdev) ab8500->chip_id >> 4, ab8500->chip_id & 0x0F); - /* Configure AB8500 or AB9540 IRQ */ - if (is_ab9540(ab8500) || is_ab8505(ab8500)) { + /* Configure AB8540 */ + if (is_ab8540(ab8500)) { + ab8500->mask_size = AB8540_NUM_IRQ_REGS; + ab8500->irq_reg_offset = ab8540_irq_regoffset; + ab8500->it_latchhier_num = AB8540_IT_LATCHHIER_NUM; + }/* Configure AB8500 or AB9540 IRQ */ + else if (is_ab9540(ab8500) || is_ab8505(ab8500)) { ab8500->mask_size = AB9540_NUM_IRQ_REGS; ab8500->irq_reg_offset = ab9540_irq_regoffset; + ab8500->it_latchhier_num = AB8500_IT_LATCHHIER_NUM; } else { ab8500->mask_size = AB8500_NUM_IRQ_REGS; ab8500->irq_reg_offset = ab8500_irq_regoffset; + ab8500->it_latchhier_num = AB8500_IT_LATCHHIER_NUM; } ab8500->mask = devm_kzalloc(&pdev->dev, ab8500->mask_size, GFP_KERNEL); if (!ab8500->mask) @@ -1396,10 +1644,36 @@ static int ab8500_probe(struct platform_device *pdev) } else { printk(KERN_CONT " None\n"); } + ret = get_register_interruptible(ab8500, AB8500_SYS_CTRL1_BLOCK, + AB8500_TURN_ON_STATUS, &value); + if (ret < 0) + return ret; + dev_info(ab8500->dev, "turn on reason(s) (%#x): ", value); + + if (value) { + for (i = 0; i < ARRAY_SIZE(turn_on_status); i++) { + if (value & 1) + printk("\"%s\" ", turn_on_status[i]); + value = value >> 1; + } + printk("\n"); + } else { + printk("None\n"); + } if (plat && plat->init) plat->init(ab8500); + if (is_ab9540(ab8500)) { + ret = get_register_interruptible(ab8500, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &value); + if (ret < 0) + return ret; + if ((value & VBUS_DET_DBNC1) && (value & VBUS_DET_DBNC100)) + ab8500_override_turn_on_stat(~AB8500_POW_KEY_1_ON, + AB8500_VBUS_DET); + } + /* Clear and mask all interrupts */ for (i = 0; i < ab8500->mask_size; i++) { /* @@ -1410,6 +1684,9 @@ static int ab8500_probe(struct platform_device *pdev) is_ab8500_1p1_or_earlier(ab8500)) continue; + if (ab8500->irq_reg_offset[i] < 0) + continue; + get_register_interruptible(ab8500, AB8500_INTERRUPT, AB8500_IT_LATCH1_REG + ab8500->irq_reg_offset[i], &value); @@ -1428,26 +1705,10 @@ static int ab8500_probe(struct platform_device *pdev) if (ret) return ret; - /* Activate this feature only in ab9540 */ - /* till tests are done on ab8500 1p2 or later*/ - if (is_ab9540(ab8500)) { - ret = devm_request_threaded_irq(&pdev->dev, ab8500->irq, NULL, - ab8500_hierarchical_irq, - IRQF_ONESHOT | IRQF_NO_SUSPEND, - "ab8500", ab8500); - } - else { - ret = devm_request_threaded_irq(&pdev->dev, ab8500->irq, NULL, - ab8500_irq, - IRQF_ONESHOT | IRQF_NO_SUSPEND, - "ab8500", ab8500); - if (ret) - return ret; - } - - ret = mfd_add_devices(ab8500->dev, 0, abx500_common_devs, - ARRAY_SIZE(abx500_common_devs), NULL, - ab8500->irq_base, ab8500->domain); + ret = devm_request_threaded_irq(&pdev->dev, ab8500->irq, NULL, + ab8500_hierarchical_irq, + IRQF_ONESHOT | IRQF_NO_SUSPEND, + "ab8500", ab8500); if (ret) return ret; @@ -1455,6 +1716,14 @@ static int ab8500_probe(struct platform_device *pdev) ret = mfd_add_devices(ab8500->dev, 0, ab9540_devs, ARRAY_SIZE(ab9540_devs), NULL, ab8500->irq_base, ab8500->domain); + else if (is_ab8540(ab8500)) + ret = mfd_add_devices(ab8500->dev, 0, ab8540_devs, + ARRAY_SIZE(ab8540_devs), NULL, + ab8500->irq_base, ab8500->domain); + else if (is_ab8505(ab8500)) + ret = mfd_add_devices(ab8500->dev, 0, ab8505_devs, + ARRAY_SIZE(ab8505_devs), NULL, + ab8500->irq_base, ab8500->domain); else ret = mfd_add_devices(ab8500->dev, 0, ab8500_devs, ARRAY_SIZE(ab8500_devs), NULL, @@ -1462,13 +1731,6 @@ static int ab8500_probe(struct platform_device *pdev) if (ret) return ret; - if (is_ab9540(ab8500) || is_ab8505(ab8500)) - ret = mfd_add_devices(ab8500->dev, 0, ab9540_ab8505_devs, - ARRAY_SIZE(ab9540_ab8505_devs), NULL, - ab8500->irq_base, ab8500->domain); - if (ret) - return ret; - if (!no_bm) { /* Add battery management devices */ ret = mfd_add_devices(ab8500->dev, 0, ab8500_bm_devs, @@ -1478,12 +1740,19 @@ static int ab8500_probe(struct platform_device *pdev) dev_err(ab8500->dev, "error adding bm devices\n"); } - if (is_ab9540(ab8500)) + if (((is_ab8505(ab8500) || is_ab9540(ab8500)) && + ab8500->chip_id >= AB8500_CUT2P0) || is_ab8540(ab8500)) ret = sysfs_create_group(&ab8500->dev->kobj, &ab9540_attr_group); else ret = sysfs_create_group(&ab8500->dev->kobj, &ab8500_attr_group); + + if ((is_ab8505(ab8500) || is_ab9540(ab8500)) && + ab8500->chip_id >= AB8500_CUT2P0) + ret = sysfs_create_group(&ab8500->dev->kobj, + &ab8505_attr_group); + if (ret) dev_err(ab8500->dev, "error creating sysfs entries\n"); @@ -1494,11 +1763,16 @@ static int ab8500_remove(struct platform_device *pdev) { struct ab8500 *ab8500 = platform_get_drvdata(pdev); - if (is_ab9540(ab8500)) + if (((is_ab8505(ab8500) || is_ab9540(ab8500)) && + ab8500->chip_id >= AB8500_CUT2P0) || is_ab8540(ab8500)) sysfs_remove_group(&ab8500->dev->kobj, &ab9540_attr_group); else sysfs_remove_group(&ab8500->dev->kobj, &ab8500_attr_group); + if ((is_ab8505(ab8500) || is_ab9540(ab8500)) && + ab8500->chip_id >= AB8500_CUT2P0) + sysfs_remove_group(&ab8500->dev->kobj, &ab8505_attr_group); + mfd_remove_devices(ab8500->dev); return 0; diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index 45fe3c50eb03..b88bbbc15f1e 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -80,6 +80,7 @@ #include <linux/interrupt.h> #include <linux/kobject.h> #include <linux/slab.h> +#include <linux/irq.h> #include <linux/mfd/abx500.h> #include <linux/mfd/abx500/ab8500.h> @@ -90,6 +91,9 @@ #include <linux/ctype.h> #endif +/* TODO: this file should not reference IRQ_DB8500_AB8500! */ +#include <mach/irqs.h> + static u32 debug_bank; static u32 debug_address; @@ -101,6 +105,11 @@ static int num_irqs; static struct device_attribute **dev_attr; static char **event_name; +static u8 avg_sample = SAMPLE_16; +static u8 trig_edge = RISING_EDGE; +static u8 conv_type = ADC_SW; +static u8 trig_timer; + /** * struct ab8500_reg_range * @first: the first address of the range @@ -150,7 +159,9 @@ static struct hwreg_cfg hwreg_cfg = { #define AB8500_REV_REG 0x80 -static struct ab8500_prcmu_ranges debug_ranges[AB8500_NUM_BANKS] = { +static struct ab8500_prcmu_ranges *debug_ranges; + +struct ab8500_prcmu_ranges ab8500_debug_ranges[AB8500_NUM_BANKS] = { [0x0] = { .num_ranges = 0, .range = NULL, @@ -354,7 +365,7 @@ static struct ab8500_prcmu_ranges debug_ranges[AB8500_NUM_BANKS] = { }, { .first = 0xf5, - .last = 0xf6, + .last = 0xf6, }, }, }, @@ -479,6 +490,781 @@ static struct ab8500_prcmu_ranges debug_ranges[AB8500_NUM_BANKS] = { }, }; +struct ab8500_prcmu_ranges ab8505_debug_ranges[AB8500_NUM_BANKS] = { + [0x0] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_SYS_CTRL1_BLOCK] = { + .num_ranges = 5, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x42, + .last = 0x42, + }, + { + .first = 0x52, + .last = 0x52, + }, + { + .first = 0x54, + .last = 0x57, + }, + { + .first = 0x80, + .last = 0x83, + }, + }, + }, + [AB8500_SYS_CTRL2_BLOCK] = { + .num_ranges = 5, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x0D, + }, + { + .first = 0x0F, + .last = 0x17, + }, + { + .first = 0x20, + .last = 0x20, + }, + { + .first = 0x30, + .last = 0x30, + }, + { + .first = 0x32, + .last = 0x3A, + }, + }, + }, + [AB8500_REGU_CTRL1] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x03, + .last = 0x11, + }, + { + .first = 0x80, + .last = 0x86, + }, + }, + }, + [AB8500_REGU_CTRL2] = { + .num_ranges = 6, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x06, + }, + { + .first = 0x08, + .last = 0x15, + }, + { + .first = 0x17, + .last = 0x19, + }, + { + .first = 0x1B, + .last = 0x1D, + }, + { + .first = 0x1F, + .last = 0x30, + }, + { + .first = 0x40, + .last = 0x48, + }, + /* 0x80-0x8B is SIM registers and should + * not be accessed from here */ + }, + }, + [AB8500_USB] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x87, + .last = 0x8A, + }, + { + .first = 0x91, + .last = 0x94, + }, + }, + }, + [AB8500_TVOUT] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_DBI] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_ECI_AV_ACC] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [AB8500_RESERVED] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_GPADC] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x08, + }, + }, + }, + [AB8500_CHARGER] = { + .num_ranges = 9, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x02, + .last = 0x03, + }, + { + .first = 0x05, + .last = 0x05, + }, + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x50, + .last = 0x57, + }, + { + .first = 0x60, + .last = 0x60, + }, + { + .first = 0xA0, + .last = 0xA7, + }, + { + .first = 0xAF, + .last = 0xB2, + }, + { + .first = 0xC0, + .last = 0xC2, + }, + { + .first = 0xF5, + .last = 0xF5, + }, + }, + }, + [AB8500_GAS_GAUGE] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x07, + .last = 0x0A, + }, + { + .first = 0x10, + .last = 0x14, + }, + }, + }, + [AB8500_AUDIO] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x83, + }, + }, + }, + [AB8500_INTERRUPT] = { + .num_ranges = 11, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x06, + .last = 0x07, + }, + { + .first = 0x09, + .last = 0x09, + }, + { + .first = 0x0B, + .last = 0x0C, + }, + { + .first = 0x12, + .last = 0x15, + }, + { + .first = 0x18, + .last = 0x18, + }, + /* Latch registers should not be read here */ + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x46, + .last = 0x49, + }, + { + .first = 0x4B, + .last = 0x4D, + }, + { + .first = 0x52, + .last = 0x55, + }, + { + .first = 0x58, + .last = 0x58, + }, + /* LatchHier registers should not be read here */ + }, + }, + [AB8500_RTC] = { + .num_ranges = 2, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x14, + }, + { + .first = 0x16, + .last = 0x17, + }, + }, + }, + [AB8500_MISC] = { + .num_ranges = 8, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x06, + }, + { + .first = 0x10, + .last = 0x16, + }, + { + .first = 0x20, + .last = 0x26, + }, + { + .first = 0x30, + .last = 0x36, + }, + { + .first = 0x40, + .last = 0x46, + }, + { + .first = 0x50, + .last = 0x50, + }, + { + .first = 0x60, + .last = 0x6B, + }, + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [AB8500_DEVELOPMENT] = { + .num_ranges = 2, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x05, + .last = 0x05, + }, + }, + }, + [AB8500_DEBUG] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x05, + .last = 0x07, + }, + }, + }, + [AB8500_PROD_TEST] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_STE_TEST] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_OTP_EMUL] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x01, + .last = 0x15, + }, + }, + }, +}; + +struct ab8500_prcmu_ranges ab8540_debug_ranges[AB8500_NUM_BANKS] = { + [AB8500_M_FSM_RANK] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x0B, + }, + }, + }, + [AB8500_SYS_CTRL1_BLOCK] = { + .num_ranges = 6, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x42, + .last = 0x42, + }, + { + .first = 0x50, + .last = 0x54, + }, + { + .first = 0x57, + .last = 0x57, + }, + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x90, + .last = 0x90, + }, + }, + }, + [AB8500_SYS_CTRL2_BLOCK] = { + .num_ranges = 5, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x0D, + }, + { + .first = 0x0F, + .last = 0x10, + }, + { + .first = 0x20, + .last = 0x21, + }, + { + .first = 0x32, + .last = 0x3C, + }, + { + .first = 0x40, + .last = 0x42, + }, + }, + }, + [AB8500_REGU_CTRL1] = { + .num_ranges = 4, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x03, + .last = 0x15, + }, + { + .first = 0x20, + .last = 0x20, + }, + { + .first = 0x80, + .last = 0x85, + }, + { + .first = 0x87, + .last = 0x88, + }, + }, + }, + [AB8500_REGU_CTRL2] = { + .num_ranges = 8, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x06, + }, + { + .first = 0x08, + .last = 0x15, + }, + { + .first = 0x17, + .last = 0x19, + }, + { + .first = 0x1B, + .last = 0x1D, + }, + { + .first = 0x1F, + .last = 0x2F, + }, + { + .first = 0x31, + .last = 0x3A, + }, + { + .first = 0x43, + .last = 0x44, + }, + { + .first = 0x48, + .last = 0x49, + }, + }, + }, + [AB8500_USB] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x87, + .last = 0x8A, + }, + { + .first = 0x91, + .last = 0x94, + }, + }, + }, + [AB8500_TVOUT] = { + .num_ranges = 0, + .range = NULL + }, + [AB8500_DBI] = { + .num_ranges = 4, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x07, + }, + { + .first = 0x10, + .last = 0x11, + }, + { + .first = 0x20, + .last = 0x21, + }, + { + .first = 0x30, + .last = 0x43, + }, + }, + }, + [AB8500_ECI_AV_ACC] = { + .num_ranges = 2, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x03, + }, + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [AB8500_RESERVED] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_GPADC] = { + .num_ranges = 4, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + }, + { + .first = 0x04, + .last = 0x06, + }, + { + .first = 0x09, + .last = 0x0A, + }, + { + .first = 0x10, + .last = 0x14, + }, + }, + }, + [AB8500_CHARGER] = { + .num_ranges = 10, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x02, + .last = 0x05, + }, + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x50, + .last = 0x57, + }, + { + .first = 0x60, + .last = 0x60, + }, + { + .first = 0x70, + .last = 0x70, + }, + { + .first = 0xA0, + .last = 0xA9, + }, + { + .first = 0xAF, + .last = 0xB2, + }, + { + .first = 0xC0, + .last = 0xC6, + }, + { + .first = 0xF5, + .last = 0xF5, + }, + }, + }, + [AB8500_GAS_GAUGE] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x07, + .last = 0x0A, + }, + { + .first = 0x10, + .last = 0x14, + }, + }, + }, + [AB8500_AUDIO] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x9f, + }, + }, + }, + [AB8500_INTERRUPT] = { + .num_ranges = 6, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x05, + }, + { + .first = 0x0B, + .last = 0x0D, + }, + { + .first = 0x12, + .last = 0x20, + }, + /* Latch registers should not be read here */ + { + .first = 0x40, + .last = 0x45, + }, + { + .first = 0x4B, + .last = 0x4D, + }, + { + .first = 0x52, + .last = 0x60, + }, + /* LatchHier registers should not be read here */ + }, + }, + [AB8500_RTC] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x07, + }, + { + .first = 0x0B, + .last = 0x18, + }, + { + .first = 0x20, + .last = 0x25, + }, + }, + }, + [AB8500_MISC] = { + .num_ranges = 9, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x06, + }, + { + .first = 0x10, + .last = 0x16, + }, + { + .first = 0x20, + .last = 0x26, + }, + { + .first = 0x30, + .last = 0x36, + }, + { + .first = 0x40, + .last = 0x49, + }, + { + .first = 0x50, + .last = 0x50, + }, + { + .first = 0x60, + .last = 0x6B, + }, + { + .first = 0x70, + .last = 0x74, + }, + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [AB8500_DEVELOPMENT] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + }, + { + .first = 0x06, + .last = 0x06, + }, + { + .first = 0x10, + .last = 0x21, + }, + }, + }, + [AB8500_DEBUG] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x01, + .last = 0x0C, + }, + { + .first = 0x0E, + .last = 0x11, + }, + { + .first = 0x80, + .last = 0x81, + }, + }, + }, + [AB8500_PROD_TEST] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_STE_TEST] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_OTP_EMUL] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x3F, + }, + }, + }, +}; + + static irqreturn_t ab8500_debug_handler(int irq, void *data) { char buf[16]; @@ -520,19 +1306,16 @@ static int ab8500_registers_print(struct device *dev, u32 bank, } if (s) { - err = seq_printf(s, " [%u/0x%02X]: 0x%02X\n", + err = seq_printf(s, " [0x%02X/0x%02X]: 0x%02X\n", bank, reg, value); if (err < 0) { - dev_err(dev, - "seq_printf overflow bank=%d reg=%d\n", - bank, reg); /* Error is not returned here since * the output is wanted in any case */ return 0; } } else { - printk(KERN_INFO" [%u/0x%02X]: 0x%02X\n", bank, - reg, value); + printk(KERN_INFO" [0x%02X/0x%02X]: 0x%02X\n", + bank, reg, value); } } } @@ -546,7 +1329,7 @@ static int ab8500_print_bank_registers(struct seq_file *s, void *p) seq_printf(s, AB8500_NAME_STRING " register values:\n"); - seq_printf(s, " bank %u:\n", bank); + seq_printf(s, " bank 0x%02X:\n", bank); ab8500_registers_print(dev, bank, s); return 0; @@ -573,10 +1356,8 @@ static int ab8500_print_all_banks(struct seq_file *s, void *p) seq_printf(s, AB8500_NAME_STRING " register values:\n"); - for (i = 1; i < AB8500_NUM_BANKS; i++) { - err = seq_printf(s, " bank %u:\n", i); - if (err < 0) - dev_err(dev, "seq_printf overflow, bank=%d\n", i); + for (i = 0; i < AB8500_NUM_BANKS; i++) { + err = seq_printf(s, " bank 0x%02X:\n", i); ab8500_registers_print(dev, i, s); } @@ -591,11 +1372,68 @@ void ab8500_dump_all_banks(struct device *dev) printk(KERN_INFO"ab8500 register values:\n"); for (i = 1; i < AB8500_NUM_BANKS; i++) { - printk(KERN_INFO" bank %u:\n", i); + printk(KERN_INFO" bank 0x%02X:\n", i); ab8500_registers_print(dev, i, NULL); } } +/* Space for 500 registers. */ +#define DUMP_MAX_REGS 700 +struct ab8500_register_dump +{ + u8 bank; + u8 reg; + u8 value; +} ab8500_complete_register_dump[DUMP_MAX_REGS]; + +extern int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size); + +/* This shall only be called upon kernel panic! */ +void ab8500_dump_all_banks_to_mem(void) +{ + int i, r = 0; + u8 bank; + int err = 0; + + pr_info("Saving all ABB registers at \"ab8500_complete_register_dump\" " + "for crash analyze.\n"); + + for (bank = 0; bank < AB8500_NUM_BANKS; bank++) { + for (i = 0; i < debug_ranges[bank].num_ranges; i++) { + u8 reg; + + for (reg = debug_ranges[bank].range[i].first; + reg <= debug_ranges[bank].range[i].last; + reg++) { + u8 value; + + err = prcmu_abb_read(bank, reg, &value, 1); + + if (err < 0) + goto out; + + ab8500_complete_register_dump[r].bank = bank; + ab8500_complete_register_dump[r].reg = reg; + ab8500_complete_register_dump[r].value = value; + + r++; + + if (r >= DUMP_MAX_REGS) { + pr_err("%s: too many register to dump!\n", + __func__); + err = -EINVAL; + goto out; + } + } + } + } +out: + if (err >= 0) + pr_info("Saved all ABB registers.\n"); + else + pr_info("Failed to save all ABB registers.\n"); +} + static int ab8500_all_banks_open(struct inode *inode, struct file *file) { struct seq_file *s; @@ -625,7 +1463,7 @@ static const struct file_operations ab8500_all_banks_fops = { static int ab8500_bank_print(struct seq_file *s, void *p) { - return seq_printf(s, "%d\n", debug_bank); + return seq_printf(s, "0x%02X\n", debug_bank); } static int ab8500_bank_open(struct inode *inode, struct file *file) @@ -641,7 +1479,6 @@ static ssize_t ab8500_bank_write(struct file *file, unsigned long user_bank; int err; - /* Get userspace string and assure termination */ err = kstrtoul_from_user(user_buf, count, 0, &user_bank); if (err) return err; @@ -667,14 +1504,13 @@ static int ab8500_address_open(struct inode *inode, struct file *file) } static ssize_t ab8500_address_write(struct file *file, - const char __user *user_buf, - size_t count, loff_t *ppos) + const char __user *user_buf, + size_t count, loff_t *ppos) { struct device *dev = ((struct seq_file *)(file->private_data))->private; unsigned long user_address; int err; - /* Get userspace string and assure termination */ err = kstrtoul_from_user(user_buf, count, 0, &user_address); if (err) return err; @@ -684,6 +1520,7 @@ static ssize_t ab8500_address_write(struct file *file, return -EINVAL; } debug_address = user_address; + return count; } @@ -711,14 +1548,13 @@ static int ab8500_val_open(struct inode *inode, struct file *file) } static ssize_t ab8500_val_write(struct file *file, - const char __user *user_buf, - size_t count, loff_t *ppos) + const char __user *user_buf, + size_t count, loff_t *ppos) { struct device *dev = ((struct seq_file *)(file->private_data))->private; unsigned long user_val; int err; - /* Get userspace string and assure termination */ err = kstrtoul_from_user(user_buf, count, 0, &user_val); if (err) return err; @@ -741,22 +1577,46 @@ static ssize_t ab8500_val_write(struct file *file, * Interrupt status */ static u32 num_interrupts[AB8500_MAX_NR_IRQS]; +static u32 num_wake_interrupts[AB8500_MAX_NR_IRQS]; static int num_interrupt_lines; +bool __attribute__((weak)) suspend_test_wake_cause_interrupt_is_mine(u32 my_int) +{ + return false; +} + void ab8500_debug_register_interrupt(int line) { - if (line < num_interrupt_lines) + if (line < num_interrupt_lines) { num_interrupts[line]++; + if (suspend_test_wake_cause_interrupt_is_mine(IRQ_DB8500_AB8500)) + num_wake_interrupts[line]++; + } } static int ab8500_interrupts_print(struct seq_file *s, void *p) { int line; - seq_printf(s, "irq: number of\n"); + seq_printf(s, "name: number: number of: wake:\n"); - for (line = 0; line < num_interrupt_lines; line++) - seq_printf(s, "%3i: %6i\n", line, num_interrupts[line]); + for (line = 0; line < num_interrupt_lines; line++) { + struct irq_desc *desc = irq_to_desc(line + irq_first); + struct irqaction *action = desc->action; + + seq_printf(s, "%3i: %6i %4i", line, + num_interrupts[line], + num_wake_interrupts[line]); + + if (desc && desc->name) + seq_printf(s, "-%-8s", desc->name); + if (action) { + seq_printf(s, " %s", action->name); + while ((action = action->next) != NULL) + seq_printf(s, ", %s", action->name); + } + seq_putc(s, '\n'); + } return 0; } @@ -801,6 +1661,79 @@ static int ab8500_hwreg_open(struct inode *inode, struct file *file) return single_open(file, ab8500_hwreg_print, inode->i_private); } +#define AB8500_SUPPLY_CONTROL_CONFIG_1 0x01 +#define AB8500_SUPPLY_CONTROL_REG 0x00 +#define AB8500_FIRST_SIM_REG 0x80 +#define AB8500_LAST_SIM_REG 0x8B +#define AB8505_LAST_SIM_REG 0x8C + +static int ab8500_print_modem_registers(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + struct ab8500 *ab8500; + int err; + u8 value; + u8 orig_value; + u32 bank = AB8500_REGU_CTRL2; + u32 last_sim_reg = AB8500_LAST_SIM_REG; + u32 reg; + + ab8500 = dev_get_drvdata(dev->parent); + dev_warn(dev, "WARNING! This operation can interfer with modem side\n" + "and should only be done with care\n"); + + err = abx500_get_register_interruptible(dev, + AB8500_REGU_CTRL1, AB8500_SUPPLY_CONTROL_REG, &orig_value); + if (err < 0) { + dev_err(dev, "ab->read fail %d\n", err); + return err; + } + /* Config 1 will allow APE side to read SIM registers */ + err = abx500_set_register_interruptible(dev, + AB8500_REGU_CTRL1, AB8500_SUPPLY_CONTROL_REG, + AB8500_SUPPLY_CONTROL_CONFIG_1); + if (err < 0) { + dev_err(dev, "ab->write fail %d\n", err); + return err; + } + + seq_printf(s, " bank 0x%02X:\n", bank); + + if (is_ab9540(ab8500) || is_ab8505(ab8500)) + last_sim_reg = AB8505_LAST_SIM_REG; + + for (reg = AB8500_FIRST_SIM_REG; reg <= last_sim_reg; reg++) { + err = abx500_get_register_interruptible(dev, + bank, reg, &value); + if (err < 0) { + dev_err(dev, "ab->read fail %d\n", err); + return err; + } + err = seq_printf(s, " [0x%02X/0x%02X]: 0x%02X\n", + bank, reg, value); + } + err = abx500_set_register_interruptible(dev, + AB8500_REGU_CTRL1, AB8500_SUPPLY_CONTROL_REG, orig_value); + if (err < 0) { + dev_err(dev, "ab->write fail %d\n", err); + return err; + } + return 0; +} + +static int ab8500_modem_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_print_modem_registers, inode->i_private); +} + +static const struct file_operations ab8500_modem_fops = { + .open = ab8500_modem_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + static int ab8500_gpadc_bat_ctrl_print(struct seq_file *s, void *p) { int bat_ctrl_raw; @@ -808,12 +1741,13 @@ static int ab8500_gpadc_bat_ctrl_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - bat_ctrl_raw = ab8500_gpadc_read_raw(gpadc, BAT_CTRL); + bat_ctrl_raw = ab8500_gpadc_read_raw(gpadc, BAT_CTRL, + avg_sample, trig_edge, trig_timer, conv_type); bat_ctrl_convert = ab8500_gpadc_ad_to_voltage(gpadc, - BAT_CTRL, bat_ctrl_raw); + BAT_CTRL, bat_ctrl_raw); return seq_printf(s, "%d,0x%X\n", - bat_ctrl_convert, bat_ctrl_raw); + bat_ctrl_convert, bat_ctrl_raw); } static int ab8500_gpadc_bat_ctrl_open(struct inode *inode, struct file *file) @@ -836,16 +1770,17 @@ static int ab8500_gpadc_btemp_ball_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - btemp_ball_raw = ab8500_gpadc_read_raw(gpadc, BTEMP_BALL); + btemp_ball_raw = ab8500_gpadc_read_raw(gpadc, BTEMP_BALL, + avg_sample, trig_edge, trig_timer, conv_type); btemp_ball_convert = ab8500_gpadc_ad_to_voltage(gpadc, BTEMP_BALL, - btemp_ball_raw); + btemp_ball_raw); return seq_printf(s, - "%d,0x%X\n", btemp_ball_convert, btemp_ball_raw); + "%d,0x%X\n", btemp_ball_convert, btemp_ball_raw); } static int ab8500_gpadc_btemp_ball_open(struct inode *inode, - struct file *file) + struct file *file) { return single_open(file, ab8500_gpadc_btemp_ball_print, inode->i_private); } @@ -865,19 +1800,20 @@ static int ab8500_gpadc_main_charger_v_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - main_charger_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_V); + main_charger_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_V, + avg_sample, trig_edge, trig_timer, conv_type); main_charger_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, - MAIN_CHARGER_V, main_charger_v_raw); + MAIN_CHARGER_V, main_charger_v_raw); return seq_printf(s, "%d,0x%X\n", main_charger_v_convert, main_charger_v_raw); } static int ab8500_gpadc_main_charger_v_open(struct inode *inode, - struct file *file) + struct file *file) { return single_open(file, ab8500_gpadc_main_charger_v_print, - inode->i_private); + inode->i_private); } static const struct file_operations ab8500_gpadc_main_charger_v_fops = { @@ -895,19 +1831,20 @@ static int ab8500_gpadc_acc_detect1_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - acc_detect1_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT1); + acc_detect1_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT1, + avg_sample, trig_edge, trig_timer, conv_type); acc_detect1_convert = ab8500_gpadc_ad_to_voltage(gpadc, ACC_DETECT1, - acc_detect1_raw); + acc_detect1_raw); return seq_printf(s, "%d,0x%X\n", - acc_detect1_convert, acc_detect1_raw); + acc_detect1_convert, acc_detect1_raw); } static int ab8500_gpadc_acc_detect1_open(struct inode *inode, - struct file *file) + struct file *file) { return single_open(file, ab8500_gpadc_acc_detect1_print, - inode->i_private); + inode->i_private); } static const struct file_operations ab8500_gpadc_acc_detect1_fops = { @@ -925,19 +1862,20 @@ static int ab8500_gpadc_acc_detect2_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - acc_detect2_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT2); + acc_detect2_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT2, + avg_sample, trig_edge, trig_timer, conv_type); acc_detect2_convert = ab8500_gpadc_ad_to_voltage(gpadc, - ACC_DETECT2, acc_detect2_raw); + ACC_DETECT2, acc_detect2_raw); return seq_printf(s, "%d,0x%X\n", - acc_detect2_convert, acc_detect2_raw); + acc_detect2_convert, acc_detect2_raw); } static int ab8500_gpadc_acc_detect2_open(struct inode *inode, struct file *file) { return single_open(file, ab8500_gpadc_acc_detect2_print, - inode->i_private); + inode->i_private); } static const struct file_operations ab8500_gpadc_acc_detect2_fops = { @@ -955,12 +1893,13 @@ static int ab8500_gpadc_aux1_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - aux1_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX1); + aux1_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX1, + avg_sample, trig_edge, trig_timer, conv_type); aux1_convert = ab8500_gpadc_ad_to_voltage(gpadc, ADC_AUX1, - aux1_raw); + aux1_raw); return seq_printf(s, "%d,0x%X\n", - aux1_convert, aux1_raw); + aux1_convert, aux1_raw); } static int ab8500_gpadc_aux1_open(struct inode *inode, struct file *file) @@ -983,9 +1922,10 @@ static int ab8500_gpadc_aux2_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - aux2_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX2); + aux2_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX2, + avg_sample, trig_edge, trig_timer, conv_type); aux2_convert = ab8500_gpadc_ad_to_voltage(gpadc, ADC_AUX2, - aux2_raw); + aux2_raw); return seq_printf(s, "%d,0x%X\n", aux2_convert, aux2_raw); @@ -1011,16 +1951,17 @@ static int ab8500_gpadc_main_bat_v_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - main_bat_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_BAT_V); + main_bat_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_BAT_V, + avg_sample, trig_edge, trig_timer, conv_type); main_bat_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, MAIN_BAT_V, - main_bat_v_raw); + main_bat_v_raw); return seq_printf(s, "%d,0x%X\n", - main_bat_v_convert, main_bat_v_raw); + main_bat_v_convert, main_bat_v_raw); } static int ab8500_gpadc_main_bat_v_open(struct inode *inode, - struct file *file) + struct file *file) { return single_open(file, ab8500_gpadc_main_bat_v_print, inode->i_private); } @@ -1040,12 +1981,13 @@ static int ab8500_gpadc_vbus_v_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - vbus_v_raw = ab8500_gpadc_read_raw(gpadc, VBUS_V); + vbus_v_raw = ab8500_gpadc_read_raw(gpadc, VBUS_V, + avg_sample, trig_edge, trig_timer, conv_type); vbus_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, VBUS_V, - vbus_v_raw); + vbus_v_raw); return seq_printf(s, "%d,0x%X\n", - vbus_v_convert, vbus_v_raw); + vbus_v_convert, vbus_v_raw); } static int ab8500_gpadc_vbus_v_open(struct inode *inode, struct file *file) @@ -1068,19 +2010,20 @@ static int ab8500_gpadc_main_charger_c_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - main_charger_c_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_C); + main_charger_c_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_C, + avg_sample, trig_edge, trig_timer, conv_type); main_charger_c_convert = ab8500_gpadc_ad_to_voltage(gpadc, - MAIN_CHARGER_C, main_charger_c_raw); + MAIN_CHARGER_C, main_charger_c_raw); return seq_printf(s, "%d,0x%X\n", - main_charger_c_convert, main_charger_c_raw); + main_charger_c_convert, main_charger_c_raw); } static int ab8500_gpadc_main_charger_c_open(struct inode *inode, struct file *file) { return single_open(file, ab8500_gpadc_main_charger_c_print, - inode->i_private); + inode->i_private); } static const struct file_operations ab8500_gpadc_main_charger_c_fops = { @@ -1098,19 +2041,20 @@ static int ab8500_gpadc_usb_charger_c_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - usb_charger_c_raw = ab8500_gpadc_read_raw(gpadc, USB_CHARGER_C); + usb_charger_c_raw = ab8500_gpadc_read_raw(gpadc, USB_CHARGER_C, + avg_sample, trig_edge, trig_timer, conv_type); usb_charger_c_convert = ab8500_gpadc_ad_to_voltage(gpadc, - USB_CHARGER_C, usb_charger_c_raw); + USB_CHARGER_C, usb_charger_c_raw); return seq_printf(s, "%d,0x%X\n", - usb_charger_c_convert, usb_charger_c_raw); + usb_charger_c_convert, usb_charger_c_raw); } static int ab8500_gpadc_usb_charger_c_open(struct inode *inode, struct file *file) { return single_open(file, ab8500_gpadc_usb_charger_c_print, - inode->i_private); + inode->i_private); } static const struct file_operations ab8500_gpadc_usb_charger_c_fops = { @@ -1128,12 +2072,13 @@ static int ab8500_gpadc_bk_bat_v_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - bk_bat_v_raw = ab8500_gpadc_read_raw(gpadc, BK_BAT_V); + bk_bat_v_raw = ab8500_gpadc_read_raw(gpadc, BK_BAT_V, + avg_sample, trig_edge, trig_timer, conv_type); bk_bat_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, - BK_BAT_V, bk_bat_v_raw); + BK_BAT_V, bk_bat_v_raw); return seq_printf(s, "%d,0x%X\n", - bk_bat_v_convert, bk_bat_v_raw); + bk_bat_v_convert, bk_bat_v_raw); } static int ab8500_gpadc_bk_bat_v_open(struct inode *inode, struct file *file) @@ -1156,12 +2101,13 @@ static int ab8500_gpadc_die_temp_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - die_temp_raw = ab8500_gpadc_read_raw(gpadc, DIE_TEMP); + die_temp_raw = ab8500_gpadc_read_raw(gpadc, DIE_TEMP, + avg_sample, trig_edge, trig_timer, conv_type); die_temp_convert = ab8500_gpadc_ad_to_voltage(gpadc, DIE_TEMP, - die_temp_raw); + die_temp_raw); return seq_printf(s, "%d,0x%X\n", - die_temp_convert, die_temp_raw); + die_temp_convert, die_temp_raw); } static int ab8500_gpadc_die_temp_open(struct inode *inode, struct file *file) @@ -1177,6 +2123,453 @@ static const struct file_operations ab8500_gpadc_die_temp_fops = { .owner = THIS_MODULE, }; +static int ab8500_gpadc_usb_id_print(struct seq_file *s, void *p) +{ + int usb_id_raw; + int usb_id_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + usb_id_raw = ab8500_gpadc_read_raw(gpadc, USB_ID, + avg_sample, trig_edge, trig_timer, conv_type); + usb_id_convert = ab8500_gpadc_ad_to_voltage(gpadc, USB_ID, + usb_id_raw); + + return seq_printf(s, "%d,0x%X\n", + usb_id_convert, usb_id_raw); +} + +static int ab8500_gpadc_usb_id_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_usb_id_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_usb_id_fops = { + .open = ab8500_gpadc_usb_id_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_xtal_temp_print(struct seq_file *s, void *p) +{ + int xtal_temp_raw; + int xtal_temp_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + xtal_temp_raw = ab8500_gpadc_read_raw(gpadc, XTAL_TEMP, + avg_sample, trig_edge, trig_timer, conv_type); + xtal_temp_convert = ab8500_gpadc_ad_to_voltage(gpadc, XTAL_TEMP, + xtal_temp_raw); + + return seq_printf(s, "%d,0x%X\n", + xtal_temp_convert, xtal_temp_raw); +} + +static int ab8540_gpadc_xtal_temp_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8540_gpadc_xtal_temp_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_xtal_temp_fops = { + .open = ab8540_gpadc_xtal_temp_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_vbat_true_meas_print(struct seq_file *s, void *p) +{ + int vbat_true_meas_raw; + int vbat_true_meas_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + vbat_true_meas_raw = ab8500_gpadc_read_raw(gpadc, VBAT_TRUE_MEAS, + avg_sample, trig_edge, trig_timer, conv_type); + vbat_true_meas_convert = ab8500_gpadc_ad_to_voltage(gpadc, VBAT_TRUE_MEAS, + vbat_true_meas_raw); + + return seq_printf(s, "%d,0x%X\n", + vbat_true_meas_convert, vbat_true_meas_raw); +} + +static int ab8540_gpadc_vbat_true_meas_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_vbat_true_meas_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_vbat_true_meas_fops = { + .open = ab8540_gpadc_vbat_true_meas_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_bat_ctrl_and_ibat_print(struct seq_file *s, void *p) +{ + int bat_ctrl_raw; + int bat_ctrl_convert; + int ibat_raw; + int ibat_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + bat_ctrl_raw = ab8500_gpadc_double_read_raw(gpadc, BAT_CTRL_AND_IBAT, + avg_sample, trig_edge, trig_timer, conv_type, &ibat_raw); + + bat_ctrl_convert = ab8500_gpadc_ad_to_voltage(gpadc, BAT_CTRL, + bat_ctrl_raw); + ibat_convert = ab8500_gpadc_ad_to_voltage(gpadc, IBAT_VIRTUAL_CHANNEL, + ibat_raw); + + return seq_printf(s, "%d,0x%X\n" "%d,0x%X\n", + bat_ctrl_convert, bat_ctrl_raw, + ibat_convert, ibat_raw); +} + +static int ab8540_gpadc_bat_ctrl_and_ibat_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_bat_ctrl_and_ibat_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_bat_ctrl_and_ibat_fops = { + .open = ab8540_gpadc_bat_ctrl_and_ibat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_vbat_meas_and_ibat_print(struct seq_file *s, void *p) +{ + int vbat_meas_raw; + int vbat_meas_convert; + int ibat_raw; + int ibat_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + vbat_meas_raw = ab8500_gpadc_double_read_raw(gpadc, VBAT_MEAS_AND_IBAT, + avg_sample, trig_edge, trig_timer, conv_type, &ibat_raw); + vbat_meas_convert = ab8500_gpadc_ad_to_voltage(gpadc, MAIN_BAT_V, + vbat_meas_raw); + ibat_convert = ab8500_gpadc_ad_to_voltage(gpadc, IBAT_VIRTUAL_CHANNEL, + ibat_raw); + + return seq_printf(s, "%d,0x%X\n" "%d,0x%X\n", + vbat_meas_convert, vbat_meas_raw, + ibat_convert, ibat_raw); +} + +static int ab8540_gpadc_vbat_meas_and_ibat_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_vbat_meas_and_ibat_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_vbat_meas_and_ibat_fops = { + .open = ab8540_gpadc_vbat_meas_and_ibat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_vbat_true_meas_and_ibat_print(struct seq_file *s, void *p) +{ + int vbat_true_meas_raw; + int vbat_true_meas_convert; + int ibat_raw; + int ibat_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + vbat_true_meas_raw = ab8500_gpadc_double_read_raw(gpadc, + VBAT_TRUE_MEAS_AND_IBAT, avg_sample, trig_edge, + trig_timer, conv_type, &ibat_raw); + vbat_true_meas_convert = ab8500_gpadc_ad_to_voltage(gpadc, + VBAT_TRUE_MEAS, vbat_true_meas_raw); + ibat_convert = ab8500_gpadc_ad_to_voltage(gpadc, IBAT_VIRTUAL_CHANNEL, + ibat_raw); + + return seq_printf(s, "%d,0x%X\n" "%d,0x%X\n", + vbat_true_meas_convert, vbat_true_meas_raw, + ibat_convert, ibat_raw); +} + +static int ab8540_gpadc_vbat_true_meas_and_ibat_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_vbat_true_meas_and_ibat_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_vbat_true_meas_and_ibat_fops = { + .open = ab8540_gpadc_vbat_true_meas_and_ibat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_bat_temp_and_ibat_print(struct seq_file *s, void *p) +{ + int bat_temp_raw; + int bat_temp_convert; + int ibat_raw; + int ibat_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + bat_temp_raw = ab8500_gpadc_double_read_raw(gpadc, BAT_TEMP_AND_IBAT, + avg_sample, trig_edge, trig_timer, conv_type, &ibat_raw); + bat_temp_convert = ab8500_gpadc_ad_to_voltage(gpadc, BTEMP_BALL, + bat_temp_raw); + ibat_convert = ab8500_gpadc_ad_to_voltage(gpadc, IBAT_VIRTUAL_CHANNEL, + ibat_raw); + + return seq_printf(s, "%d,0x%X\n" "%d,0x%X\n", + bat_temp_convert, bat_temp_raw, + ibat_convert, ibat_raw); +} + +static int ab8540_gpadc_bat_temp_and_ibat_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_bat_temp_and_ibat_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_bat_temp_and_ibat_fops = { + .open = ab8540_gpadc_bat_temp_and_ibat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_otp_cal_print(struct seq_file *s, void *p) +{ + struct ab8500_gpadc *gpadc; + u16 vmain_l, vmain_h, btemp_l, btemp_h; + u16 vbat_l, vbat_h, ibat_l, ibat_h; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + ab8540_gpadc_get_otp(gpadc, &vmain_l, &vmain_h, &btemp_l, &btemp_h, + &vbat_l, &vbat_h, &ibat_l, &ibat_h); + return seq_printf(s, "VMAIN_L:0x%X\n" + "VMAIN_H:0x%X\n" + "BTEMP_L:0x%X\n" + "BTEMP_H:0x%X\n" + "VBAT_L:0x%X\n" + "VBAT_H:0x%X\n" + "IBAT_L:0x%X\n" + "IBAT_H:0x%X\n", + vmain_l, vmain_h, btemp_l, btemp_h, vbat_l, vbat_h, ibat_l, ibat_h); +} + +static int ab8540_gpadc_otp_cal_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8540_gpadc_otp_cal_print, inode->i_private); +} + +static const struct file_operations ab8540_gpadc_otp_calib_fops = { + .open = ab8540_gpadc_otp_cal_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_avg_sample_print(struct seq_file *s, void *p) +{ + return seq_printf(s, "%d\n", avg_sample); +} + +static int ab8500_gpadc_avg_sample_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_avg_sample_print, + inode->i_private); +} + +static ssize_t ab8500_gpadc_avg_sample_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_avg_sample; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_avg_sample); + if (err) + return err; + + if ((user_avg_sample == SAMPLE_1) || (user_avg_sample == SAMPLE_4) + || (user_avg_sample == SAMPLE_8) + || (user_avg_sample == SAMPLE_16)) { + avg_sample = (u8) user_avg_sample; + } else { + dev_err(dev, "debugfs error input: " + "should be egal to 1, 4, 8 or 16\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations ab8500_gpadc_avg_sample_fops = { + .open = ab8500_gpadc_avg_sample_open, + .read = seq_read, + .write = ab8500_gpadc_avg_sample_write, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_trig_edge_print(struct seq_file *s, void *p) +{ + return seq_printf(s, "%d\n", trig_edge); +} + +static int ab8500_gpadc_trig_edge_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_trig_edge_print, + inode->i_private); +} + +static ssize_t ab8500_gpadc_trig_edge_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_trig_edge; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_trig_edge); + if (err) + return err; + + if ((user_trig_edge == RISING_EDGE) + || (user_trig_edge == FALLING_EDGE)) { + trig_edge = (u8) user_trig_edge; + } else { + dev_err(dev, "Wrong input:\n" + "Enter 0. Rising edge\n" + "Enter 1. Falling edge\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations ab8500_gpadc_trig_edge_fops = { + .open = ab8500_gpadc_trig_edge_open, + .read = seq_read, + .write = ab8500_gpadc_trig_edge_write, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_trig_timer_print(struct seq_file *s, void *p) +{ + return seq_printf(s, "%d\n", trig_timer); +} + +static int ab8500_gpadc_trig_timer_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_trig_timer_print, + inode->i_private); +} + +static ssize_t ab8500_gpadc_trig_timer_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_trig_timer; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_trig_timer); + if (err) + return err; + + if ((user_trig_timer >= 0) && (user_trig_timer <= 255)) { + trig_timer = (u8) user_trig_timer; + } else { + dev_err(dev, "debugfs error input: " + "should be beetween 0 to 255\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations ab8500_gpadc_trig_timer_fops = { + .open = ab8500_gpadc_trig_timer_open, + .read = seq_read, + .write = ab8500_gpadc_trig_timer_write, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_conv_type_print(struct seq_file *s, void *p) +{ + return seq_printf(s, "%d\n", conv_type); +} + +static int ab8500_gpadc_conv_type_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_conv_type_print, + inode->i_private); +} + +static ssize_t ab8500_gpadc_conv_type_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_conv_type; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_conv_type); + if (err) + return err; + + if ((user_conv_type == ADC_SW) + || (user_conv_type == ADC_HW)) { + conv_type = (u8) user_conv_type; + } else { + dev_err(dev, "Wrong input:\n" + "Enter 0. ADC SW conversion\n" + "Enter 1. ADC HW conversion\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations ab8500_gpadc_conv_type_fops = { + .open = ab8500_gpadc_conv_type_open, + .read = seq_read, + .write = ab8500_gpadc_conv_type_write, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + /* * return length of an ASCII numerical value, 0 is string is not a * numerical value. @@ -1352,7 +2745,7 @@ static int ab8500_subscribe_unsubscribe_open(struct inode *inode, struct file *file) { return single_open(file, ab8500_subscribe_unsubscribe_print, - inode->i_private); + inode->i_private); } /* @@ -1382,21 +2775,14 @@ static ssize_t ab8500_subscribe_write(struct file *file, size_t count, loff_t *ppos) { struct device *dev = ((struct seq_file *)(file->private_data))->private; - char buf[32]; - int buf_size; unsigned long user_val; int err; unsigned int irq_index; - /* Get userspace string and assure termination */ - buf_size = min(count, (sizeof(buf)-1)); - if (copy_from_user(buf, user_buf, buf_size)) - return -EFAULT; - buf[buf_size] = 0; - - err = strict_strtoul(buf, 0, &user_val); + err = kstrtoul_from_user(user_buf, count, 0, &user_val); if (err) - return -EINVAL; + return err; + if (user_val < irq_first) { dev_err(dev, "debugfs error input < %d\n", irq_first); return -EINVAL; @@ -1416,7 +2802,7 @@ static ssize_t ab8500_subscribe_write(struct file *file, */ dev_attr[irq_index] = kmalloc(sizeof(struct device_attribute), GFP_KERNEL); - event_name[irq_index] = kmalloc(buf_size, GFP_KERNEL); + event_name[irq_index] = kmalloc(count, GFP_KERNEL); sprintf(event_name[irq_index], "%lu", user_val); dev_attr[irq_index]->show = show_irq; dev_attr[irq_index]->store = NULL; @@ -1438,7 +2824,7 @@ static ssize_t ab8500_subscribe_write(struct file *file, return err; } - return buf_size; + return count; } static ssize_t ab8500_unsubscribe_write(struct file *file, @@ -1446,21 +2832,14 @@ static ssize_t ab8500_unsubscribe_write(struct file *file, size_t count, loff_t *ppos) { struct device *dev = ((struct seq_file *)(file->private_data))->private; - char buf[32]; - int buf_size; unsigned long user_val; int err; unsigned int irq_index; - /* Get userspace string and assure termination */ - buf_size = min(count, (sizeof(buf)-1)); - if (copy_from_user(buf, user_buf, buf_size)) - return -EFAULT; - buf[buf_size] = 0; - - err = strict_strtoul(buf, 0, &user_val); + err = kstrtoul_from_user(user_buf, count, 0, &user_val); if (err) - return -EINVAL; + return err; + if (user_val < irq_first) { dev_err(dev, "debugfs error input < %d\n", irq_first); return -EINVAL; @@ -1485,7 +2864,7 @@ static ssize_t ab8500_unsubscribe_write(struct file *file, kfree(event_name[irq_index]); kfree(dev_attr[irq_index]); - return buf_size; + return count; } /* @@ -1583,7 +2962,7 @@ static int ab8500_debug_probe(struct platform_device *plf) irq_first = platform_get_irq_byname(plf, "IRQ_FIRST"); if (irq_first < 0) { dev_err(&plf->dev, "First irq not found, err %d\n", - irq_first); + irq_first); ret = irq_first; goto out_freeevent_name; } @@ -1591,9 +2970,9 @@ static int ab8500_debug_probe(struct platform_device *plf) irq_last = platform_get_irq_byname(plf, "IRQ_LAST"); if (irq_last < 0) { dev_err(&plf->dev, "Last irq not found, err %d\n", - irq_last); + irq_last); ret = irq_last; - goto out_freeevent_name; + goto out_freeevent_name; } ab8500_dir = debugfs_create_dir(AB8500_NAME_STRING, NULL); @@ -1601,124 +2980,198 @@ static int ab8500_debug_probe(struct platform_device *plf) goto err; ab8500_gpadc_dir = debugfs_create_dir(AB8500_ADC_NAME_STRING, - ab8500_dir); + ab8500_dir); if (!ab8500_gpadc_dir) goto err; file = debugfs_create_file("all-bank-registers", S_IRUGO, - ab8500_dir, &plf->dev, &ab8500_registers_fops); + ab8500_dir, &plf->dev, &ab8500_registers_fops); if (!file) goto err; file = debugfs_create_file("all-banks", S_IRUGO, - ab8500_dir, &plf->dev, &ab8500_all_banks_fops); + ab8500_dir, &plf->dev, &ab8500_all_banks_fops); if (!file) goto err; - file = debugfs_create_file("register-bank", (S_IRUGO | S_IWUSR), - ab8500_dir, &plf->dev, &ab8500_bank_fops); + file = debugfs_create_file("register-bank", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_bank_fops); if (!file) goto err; - file = debugfs_create_file("register-address", (S_IRUGO | S_IWUSR), - ab8500_dir, &plf->dev, &ab8500_address_fops); + file = debugfs_create_file("register-address", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_address_fops); if (!file) goto err; - file = debugfs_create_file("register-value", (S_IRUGO | S_IWUSR), - ab8500_dir, &plf->dev, &ab8500_val_fops); + file = debugfs_create_file("register-value", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_val_fops); if (!file) goto err; - file = debugfs_create_file("irq-subscribe", (S_IRUGO | S_IWUSR), - ab8500_dir, &plf->dev, &ab8500_subscribe_fops); + file = debugfs_create_file("irq-subscribe", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_subscribe_fops); if (!file) goto err; - if (is_ab8500(ab8500)) + if (is_ab8500(ab8500)) { + debug_ranges = ab8500_debug_ranges; num_interrupt_lines = AB8500_NR_IRQS; - else if (is_ab8505(ab8500)) + } else if (is_ab8505(ab8500)) { + debug_ranges = ab8505_debug_ranges; num_interrupt_lines = AB8505_NR_IRQS; - else if (is_ab9540(ab8500)) + } else if (is_ab9540(ab8500)) { + debug_ranges = ab8505_debug_ranges; num_interrupt_lines = AB9540_NR_IRQS; + } else if (is_ab8540(ab8500)) { + debug_ranges = ab8540_debug_ranges; + num_interrupt_lines = AB8540_NR_IRQS; + } file = debugfs_create_file("interrupts", (S_IRUGO), - ab8500_dir, &plf->dev, &ab8500_interrupts_fops); + ab8500_dir, &plf->dev, &ab8500_interrupts_fops); if (!file) goto err; - file = debugfs_create_file("irq-unsubscribe", (S_IRUGO | S_IWUSR), - ab8500_dir, &plf->dev, &ab8500_unsubscribe_fops); + file = debugfs_create_file("irq-unsubscribe", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_unsubscribe_fops); if (!file) goto err; - file = debugfs_create_file("hwreg", (S_IRUGO | S_IWUSR), - ab8500_dir, &plf->dev, &ab8500_hwreg_fops); + file = debugfs_create_file("hwreg", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_hwreg_fops); if (!file) goto err; - file = debugfs_create_file("bat_ctrl", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bat_ctrl_fops); + file = debugfs_create_file("all-modem-registers", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_modem_fops); if (!file) goto err; - file = debugfs_create_file("btemp_ball", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_btemp_ball_fops); + file = debugfs_create_file("bat_ctrl", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bat_ctrl_fops); if (!file) goto err; - file = debugfs_create_file("main_charger_v", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_v_fops); + file = debugfs_create_file("btemp_ball", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_btemp_ball_fops); if (!file) goto err; - file = debugfs_create_file("acc_detect1", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect1_fops); + file = debugfs_create_file("main_charger_v", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_v_fops); if (!file) goto err; - file = debugfs_create_file("acc_detect2", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect2_fops); + file = debugfs_create_file("acc_detect1", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect1_fops); if (!file) goto err; - file = debugfs_create_file("adc_aux1", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux1_fops); + file = debugfs_create_file("acc_detect2", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect2_fops); if (!file) goto err; - file = debugfs_create_file("adc_aux2", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux2_fops); + file = debugfs_create_file("adc_aux1", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux1_fops); if (!file) goto err; - file = debugfs_create_file("main_bat_v", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_bat_v_fops); + file = debugfs_create_file("adc_aux2", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux2_fops); if (!file) goto err; - file = debugfs_create_file("vbus_v", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_vbus_v_fops); + file = debugfs_create_file("main_bat_v", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_bat_v_fops); if (!file) goto err; - file = debugfs_create_file("main_charger_c", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_c_fops); + file = debugfs_create_file("vbus_v", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_vbus_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("main_charger_c", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_c_fops); + if (!file) + goto err; + + file = debugfs_create_file("usb_charger_c", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_usb_charger_c_fops); + if (!file) + goto err; + + file = debugfs_create_file("bk_bat_v", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bk_bat_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("die_temp", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_die_temp_fops); + if (!file) + goto err; + + file = debugfs_create_file("usb_id", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_usb_id_fops); + if (!file) + goto err; + + if (is_ab8540(ab8500)) { + file = debugfs_create_file("xtal_temp", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8540_gpadc_xtal_temp_fops); + if (!file) + goto err; + file = debugfs_create_file("vbattruemeas", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8540_gpadc_vbat_true_meas_fops); + if (!file) + goto err; + file = debugfs_create_file("batctrl_and_ibat", + (S_IRUGO | S_IWUGO), ab8500_gpadc_dir, + &plf->dev, &ab8540_gpadc_bat_ctrl_and_ibat_fops); + if (!file) + goto err; + file = debugfs_create_file("vbatmeas_and_ibat", + (S_IRUGO | S_IWUGO), ab8500_gpadc_dir, + &plf->dev, + &ab8540_gpadc_vbat_meas_and_ibat_fops); + if (!file) + goto err; + file = debugfs_create_file("vbattruemeas_and_ibat", + (S_IRUGO | S_IWUGO), ab8500_gpadc_dir, + &plf->dev, + &ab8540_gpadc_vbat_true_meas_and_ibat_fops); + if (!file) + goto err; + file = debugfs_create_file("battemp_and_ibat", + (S_IRUGO | S_IWUGO), ab8500_gpadc_dir, + &plf->dev, &ab8540_gpadc_bat_temp_and_ibat_fops); + if (!file) + goto err; + file = debugfs_create_file("otp_calib", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8540_gpadc_otp_calib_fops); + if (!file) + goto err; + } + file = debugfs_create_file("avg_sample", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_avg_sample_fops); if (!file) goto err; - file = debugfs_create_file("usb_charger_c", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_usb_charger_c_fops); + file = debugfs_create_file("trig_edge", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_trig_edge_fops); if (!file) goto err; - file = debugfs_create_file("bk_bat_v", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bk_bat_v_fops); + file = debugfs_create_file("trig_timer", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_trig_timer_fops); if (!file) goto err; - file = debugfs_create_file("die_temp", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_die_temp_fops); + file = debugfs_create_file("conv_type", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_conv_type_fops); if (!file) goto err; diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index 5f341a50ee5a..5e65b28a5d09 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -37,6 +37,13 @@ #define AB8500_GPADC_AUTODATAL_REG 0x07 #define AB8500_GPADC_AUTODATAH_REG 0x08 #define AB8500_GPADC_MUX_CTRL_REG 0x09 +#define AB8540_GPADC_MANDATA2L_REG 0x09 +#define AB8540_GPADC_MANDATA2H_REG 0x0A +#define AB8540_GPADC_APEAAX_REG 0x10 +#define AB8540_GPADC_APEAAT_REG 0x11 +#define AB8540_GPADC_APEAAM_REG 0x12 +#define AB8540_GPADC_APEAAH_REG 0x13 +#define AB8540_GPADC_APEAAL_REG 0x14 /* * OTP register offsets @@ -49,19 +56,29 @@ #define AB8500_GPADC_CAL_5 0x13 #define AB8500_GPADC_CAL_6 0x14 #define AB8500_GPADC_CAL_7 0x15 +/* New calibration for 8540 */ +#define AB8540_GPADC_OTP4_REG_7 0x38 +#define AB8540_GPADC_OTP4_REG_6 0x39 +#define AB8540_GPADC_OTP4_REG_5 0x3A /* gpadc constants */ #define EN_VINTCORE12 0x04 #define EN_VTVOUT 0x02 #define EN_GPADC 0x01 #define DIS_GPADC 0x00 -#define SW_AVG_16 0x60 +#define AVG_1 0x00 +#define AVG_4 0x20 +#define AVG_8 0x40 +#define AVG_16 0x60 #define ADC_SW_CONV 0x04 #define EN_ICHAR 0x80 #define BTEMP_PULL_UP 0x08 #define EN_BUF 0x40 #define DIS_ZERO 0x00 #define GPADC_BUSY 0x01 +#define EN_FALLING 0x10 +#define EN_TRIG_EDGE 0x02 +#define EN_VBIAS_XTAL_TEMP 0x02 /* GPADC constants from AB8500 spec, UM0836 */ #define ADC_RESOLUTION 1024 @@ -80,8 +97,21 @@ #define ADC_CH_BKBAT_MIN 0 #define ADC_CH_BKBAT_MAX 3200 +/* GPADC constants from AB8540 spec */ +#define ADC_CH_IBAT_MIN (-6000) /* mA range measured by ADC for ibat*/ +#define ADC_CH_IBAT_MAX 6000 +#define ADC_CH_IBAT_MIN_V (-60) /* mV range measured by ADC for ibat*/ +#define ADC_CH_IBAT_MAX_V 60 +#define IBAT_VDROP_L (-56) /* mV */ +#define IBAT_VDROP_H 56 + /* This is used to not lose precision when dividing to get gain and offset */ -#define CALIB_SCALE 1000 +#define CALIB_SCALE 1000 +/* + * Number of bits shift used to not lose precision + * when dividing to get ibat gain. + */ +#define CALIB_SHIFT_IBAT 20 /* Time in ms before disabling regulator */ #define GPADC_AUDOSUSPEND_DELAY 1 @@ -92,6 +122,7 @@ enum cal_channels { ADC_INPUT_VMAIN = 0, ADC_INPUT_BTEMP, ADC_INPUT_VBAT, + ADC_INPUT_IBAT, NBR_CAL_INPUTS, }; @@ -102,8 +133,10 @@ enum cal_channels { * @offset: Offset of the ADC channel */ struct adc_cal_data { - u64 gain; - u64 offset; + s64 gain; + s64 offset; + u16 otp_calib_hi; + u16 otp_calib_lo; }; /** @@ -116,7 +149,10 @@ struct adc_cal_data { * the completion of gpadc conversion * @ab8500_gpadc_lock: structure of type mutex * @regu: pointer to the struct regulator - * @irq: interrupt number that is used by gpadc + * @irq_sw: interrupt number that is used by gpadc for Sw + * conversion + * @irq_hw: interrupt number that is used by gpadc for Hw + * conversion * @cal_data array of ADC calibration data structs */ struct ab8500_gpadc { @@ -126,7 +162,8 @@ struct ab8500_gpadc { struct completion ab8500_gpadc_complete; struct mutex ab8500_gpadc_lock; struct regulator *regu; - int irq; + int irq_sw; + int irq_hw; struct adc_cal_data cal_data[NBR_CAL_INPUTS]; }; @@ -171,6 +208,7 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel, gpadc->cal_data[ADC_INPUT_VMAIN].offset) / CALIB_SCALE; break; + case XTAL_TEMP: case BAT_CTRL: case BTEMP_BALL: case ACC_DETECT1: @@ -189,6 +227,7 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel, break; case MAIN_BAT_V: + case VBAT_TRUE_MEAS: /* For some reason we don't have calibrated data */ if (!gpadc->cal_data[ADC_INPUT_VBAT].gain) { res = ADC_CH_VBAT_MIN + (ADC_CH_VBAT_MAX - @@ -232,6 +271,20 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel, ADC_RESOLUTION; break; + case IBAT_VIRTUAL_CHANNEL: + /* For some reason we don't have calibrated data */ + if (!gpadc->cal_data[ADC_INPUT_IBAT].gain) { + res = ADC_CH_IBAT_MIN + (ADC_CH_IBAT_MAX - + ADC_CH_IBAT_MIN) * ad_value / + ADC_RESOLUTION; + break; + } + /* Here we can use the calibrated data */ + res = (int) (ad_value * gpadc->cal_data[ADC_INPUT_IBAT].gain + + gpadc->cal_data[ADC_INPUT_IBAT].offset) + >> CALIB_SHIFT_IBAT; + break; + default: dev_err(gpadc->dev, "unknown channel, not possible to convert\n"); @@ -244,51 +297,86 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel, EXPORT_SYMBOL(ab8500_gpadc_ad_to_voltage); /** - * ab8500_gpadc_convert() - gpadc conversion + * ab8500_gpadc_sw_hw_convert() - gpadc conversion * @channel: analog channel to be converted to digital data + * @avg_sample: number of ADC sample to average + * @trig_egde: selected ADC trig edge + * @trig_timer: selected ADC trigger delay timer + * @conv_type: selected conversion type (HW or SW conversion) * * This function converts the selected analog i/p to digital * data. */ -int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 channel) +int ab8500_gpadc_sw_hw_convert(struct ab8500_gpadc *gpadc, u8 channel, + u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type) { int ad_value; int voltage; - ad_value = ab8500_gpadc_read_raw(gpadc, channel); - if (ad_value < 0) { - dev_err(gpadc->dev, "GPADC raw value failed ch: %d\n", channel); + ad_value = ab8500_gpadc_read_raw(gpadc, channel, avg_sample, + trig_edge, trig_timer, conv_type); +/* On failure retry a second time */ + if (ad_value < 0) + ad_value = ab8500_gpadc_read_raw(gpadc, channel, avg_sample, + trig_edge, trig_timer, conv_type); +if (ad_value < 0) { + dev_err(gpadc->dev, "GPADC raw value failed ch: %d\n", + channel); return ad_value; } voltage = ab8500_gpadc_ad_to_voltage(gpadc, channel, ad_value); - if (voltage < 0) dev_err(gpadc->dev, "GPADC to voltage conversion failed ch:" " %d AD: 0x%x\n", channel, ad_value); return voltage; } -EXPORT_SYMBOL(ab8500_gpadc_convert); +EXPORT_SYMBOL(ab8500_gpadc_sw_hw_convert); /** * ab8500_gpadc_read_raw() - gpadc read * @channel: analog channel to be read + * @avg_sample: number of ADC sample to average + * @trig_edge: selected trig edge + * @trig_timer: selected ADC trigger delay timer + * @conv_type: selected conversion type (HW or SW conversion) * - * This function obtains the raw ADC value, this then needs - * to be converted by calling ab8500_gpadc_ad_to_voltage() + * This function obtains the raw ADC value for an hardware conversion, + * this then needs to be converted by calling ab8500_gpadc_ad_to_voltage() */ -int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) +int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel, + u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type) +{ + int raw_data; + raw_data = ab8500_gpadc_double_read_raw(gpadc, channel, + avg_sample, trig_edge, trig_timer, conv_type, NULL); + return raw_data; +} + +int ab8500_gpadc_double_read_raw(struct ab8500_gpadc *gpadc, u8 channel, + u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type, + int *ibat) { int ret; int looplimit = 0; - u8 val, low_data, high_data; + unsigned long completion_timeout; + u8 val, low_data, high_data, low_data2, high_data2; + u8 val_reg1 = 0; + unsigned int delay_min = 0; + unsigned int delay_max = 0; + u8 data_low_addr, data_high_addr; if (!gpadc) return -ENODEV; - mutex_lock(&gpadc->ab8500_gpadc_lock); + /* check if convertion is supported */ + if ((gpadc->irq_sw < 0) && (conv_type == ADC_SW)) + return -ENOTSUPP; + if ((gpadc->irq_hw < 0) && (conv_type == ADC_HW)) + return -ENOTSUPP; + mutex_lock(&gpadc->ab8500_gpadc_lock); /* Enable VTVout LDO this is required for GPADC */ pm_runtime_get_sync(gpadc->dev); @@ -309,16 +397,34 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) } /* Enable GPADC */ - ret = abx500_mask_and_set_register_interruptible(gpadc->dev, - AB8500_GPADC, AB8500_GPADC_CTRL1_REG, EN_GPADC, EN_GPADC); - if (ret < 0) { - dev_err(gpadc->dev, "gpadc_conversion: enable gpadc failed\n"); - goto out; + val_reg1 |= EN_GPADC; + + /* Select the channel source and set average samples */ + switch (avg_sample) { + case SAMPLE_1: + val = channel | AVG_1; + break; + case SAMPLE_4: + val = channel | AVG_4; + break; + case SAMPLE_8: + val = channel | AVG_8; + break; + default: + val = channel | AVG_16; + break; } - /* Select the channel source and set average samples to 16 */ - ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, - AB8500_GPADC_CTRL2_REG, (channel | SW_AVG_16)); + if (conv_type == ADC_HW) { + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL3_REG, val); + val_reg1 |= EN_TRIG_EDGE; + if (trig_edge) + val_reg1 |= EN_FALLING; + } + else + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL2_REG, val); if (ret < 0) { dev_err(gpadc->dev, "gpadc_conversion: set avg samples failed\n"); @@ -333,71 +439,129 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) switch (channel) { case MAIN_CHARGER_C: case USB_CHARGER_C: - ret = abx500_mask_and_set_register_interruptible(gpadc->dev, - AB8500_GPADC, AB8500_GPADC_CTRL1_REG, - EN_BUF | EN_ICHAR, - EN_BUF | EN_ICHAR); + val_reg1 |= EN_BUF | EN_ICHAR; break; case BTEMP_BALL: if (!is_ab8500_2p0_or_earlier(gpadc->parent)) { - /* Turn on btemp pull-up on ABB 3.0 */ - ret = abx500_mask_and_set_register_interruptible( - gpadc->dev, - AB8500_GPADC, AB8500_GPADC_CTRL1_REG, - EN_BUF | BTEMP_PULL_UP, - EN_BUF | BTEMP_PULL_UP); - - /* - * Delay might be needed for ABB8500 cut 3.0, if not, remove - * when hardware will be available - */ - usleep_range(1000, 1000); + val_reg1 |= EN_BUF | BTEMP_PULL_UP; + /* + * Delay might be needed for ABB8500 cut 3.0, if not, + * remove when hardware will be availible + */ + delay_min = 1000; /* Delay in micro seconds */ + delay_max = 10000; /* large range to optimise sleep mode */ break; } /* Intentional fallthrough */ default: - ret = abx500_mask_and_set_register_interruptible(gpadc->dev, - AB8500_GPADC, AB8500_GPADC_CTRL1_REG, EN_BUF, EN_BUF); + val_reg1 |= EN_BUF; break; } + + /* Write configuration to register */ + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL1_REG, val_reg1); if (ret < 0) { dev_err(gpadc->dev, - "gpadc_conversion: select falling edge failed\n"); + "gpadc_conversion: set Control register failed\n"); goto out; } - ret = abx500_mask_and_set_register_interruptible(gpadc->dev, - AB8500_GPADC, AB8500_GPADC_CTRL1_REG, ADC_SW_CONV, ADC_SW_CONV); - if (ret < 0) { - dev_err(gpadc->dev, - "gpadc_conversion: start s/w conversion failed\n"); - goto out; + if (delay_min != 0) + usleep_range(delay_min, delay_max); + + if (conv_type == ADC_HW) { + /* Set trigger delay timer */ + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_AUTO_TIMER_REG, trig_timer); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: trig timer failed\n"); + goto out; + } + completion_timeout = 2 * HZ; + data_low_addr = AB8500_GPADC_AUTODATAL_REG; + data_high_addr = AB8500_GPADC_AUTODATAH_REG; + } else { + /* Start SW conversion */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL1_REG, + ADC_SW_CONV, ADC_SW_CONV); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: start s/w conv failed\n"); + goto out; + } + completion_timeout = msecs_to_jiffies(CONVERSION_TIME); + data_low_addr = AB8500_GPADC_MANDATAL_REG; + data_high_addr = AB8500_GPADC_MANDATAH_REG; } + /* wait for completion of conversion */ if (!wait_for_completion_timeout(&gpadc->ab8500_gpadc_complete, - msecs_to_jiffies(CONVERSION_TIME))) { + completion_timeout)) { dev_err(gpadc->dev, - "timeout: didn't receive GPADC conversion interrupt\n"); + "timeout didn't receive GPADC conv interrupt\n"); ret = -EINVAL; goto out; } /* Read the converted RAW data */ - ret = abx500_get_register_interruptible(gpadc->dev, AB8500_GPADC, - AB8500_GPADC_MANDATAL_REG, &low_data); + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, data_low_addr, &low_data); if (ret < 0) { dev_err(gpadc->dev, "gpadc_conversion: read low data failed\n"); goto out; } - ret = abx500_get_register_interruptible(gpadc->dev, AB8500_GPADC, - AB8500_GPADC_MANDATAH_REG, &high_data); + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, data_high_addr, &high_data); if (ret < 0) { - dev_err(gpadc->dev, - "gpadc_conversion: read high data failed\n"); + dev_err(gpadc->dev, "gpadc_conversion: read high data failed\n"); goto out; } + /* Check if double convertion is required */ + if ((channel == BAT_CTRL_AND_IBAT) || + (channel == VBAT_MEAS_AND_IBAT) || + (channel == VBAT_TRUE_MEAS_AND_IBAT) || + (channel == BAT_TEMP_AND_IBAT)) { + + if (conv_type == ADC_HW) { + /* not supported */ + ret = -ENOTSUPP; + dev_err(gpadc->dev, + "gpadc_conversion: only SW double conversion supported\n"); + goto out; + } else { + /* Read the converted RAW data 2 */ + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8540_GPADC_MANDATA2L_REG, + &low_data2); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: read sw low data 2 failed\n"); + goto out; + } + + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8540_GPADC_MANDATA2H_REG, + &high_data2); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: read sw high data 2 failed\n"); + goto out; + } + if (ibat != NULL) { + *ibat = (high_data2 << 8) | low_data2; + } else { + dev_warn(gpadc->dev, + "gpadc_conversion: ibat not stored\n"); + } + + } + } + /* Disable GPADC */ ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_CTRL1_REG, DIS_GPADC); @@ -406,6 +570,7 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) goto out; } + /* Disable VTVout LDO this is required for GPADC */ pm_runtime_mark_last_busy(gpadc->dev); pm_runtime_put_autosuspend(gpadc->dev); @@ -422,9 +587,7 @@ out: */ (void) abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_CTRL1_REG, DIS_GPADC); - pm_runtime_put(gpadc->dev); - mutex_unlock(&gpadc->ab8500_gpadc_lock); dev_err(gpadc->dev, "gpadc_conversion: Failed to AD convert channel %d\n", channel); @@ -433,16 +596,16 @@ out: EXPORT_SYMBOL(ab8500_gpadc_read_raw); /** - * ab8500_bm_gpswadcconvend_handler() - isr for s/w gpadc conversion completion + * ab8500_bm_gpadcconvend_handler() - isr for gpadc conversion completion * @irq: irq number * @data: pointer to the data passed during request irq * - * This is a interrupt service routine for s/w gpadc conversion completion. + * This is a interrupt service routine for gpadc conversion completion. * Notifies the gpadc completion is completed and the converted raw value * can be read from the registers. * Returns IRQ status(IRQ_HANDLED) */ -static irqreturn_t ab8500_bm_gpswadcconvend_handler(int irq, void *_gpadc) +static irqreturn_t ab8500_bm_gpadcconvend_handler(int irq, void *_gpadc) { struct ab8500_gpadc *gpadc = _gpadc; @@ -461,15 +624,27 @@ static int otp_cal_regs[] = { AB8500_GPADC_CAL_7, }; +static int otp4_cal_regs[] = { + AB8540_GPADC_OTP4_REG_7, + AB8540_GPADC_OTP4_REG_6, + AB8540_GPADC_OTP4_REG_5, +}; + static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) { int i; int ret[ARRAY_SIZE(otp_cal_regs)]; u8 gpadc_cal[ARRAY_SIZE(otp_cal_regs)]; - + int ret_otp4[ARRAY_SIZE(otp4_cal_regs)]; + u8 gpadc_otp4[ARRAY_SIZE(otp4_cal_regs)]; int vmain_high, vmain_low; int btemp_high, btemp_low; int vbat_high, vbat_low; + int ibat_high, ibat_low; + s64 V_gain, V_offset, V2A_gain, V2A_offset; + struct ab8500 *ab8500; + + ab8500 = gpadc->parent; /* First we read all OTP registers and store the error code */ for (i = 0; i < ARRAY_SIZE(otp_cal_regs); i++) { @@ -489,7 +664,7 @@ static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) * bt_h/l = btemp_high/low * vb_h/l = vbat_high/low * - * Data bits: + * Data bits 8500/9540: * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 * |.......|.......|.......|.......|.......|.......|.......|....... * | | vm_h9 | vm_h8 @@ -507,6 +682,35 @@ static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) * | vb_l5 | vb_l4 | vb_l3 | vb_l2 | vb_l1 | vb_l0 | * |.......|.......|.......|.......|.......|.......|.......|....... * + * Data bits 8540: + * OTP2 + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vm_h9 | vm_h8 | vm_h7 | vm_h6 | vm_h5 | vm_h4 | vm_h3 | vm_h2 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vm_h1 | vm_h0 | vm_l4 | vm_l3 | vm_l2 | vm_l1 | vm_l0 | bt_h9 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | bt_h8 | bt_h7 | bt_h6 | bt_h5 | bt_h4 | bt_h3 | bt_h2 | bt_h1 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | bt_h0 | bt_l4 | bt_l3 | bt_l2 | bt_l1 | bt_l0 | vb_h9 | vb_h8 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vb_h7 | vb_h6 | vb_h5 | vb_h4 | vb_h3 | vb_h2 | vb_h1 | vb_h0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vb_l5 | vb_l4 | vb_l3 | vb_l2 | vb_l1 | vb_l0 | + * |.......|.......|.......|.......|.......|.......|.......|....... + * + * Data bits 8540: + * OTP4 + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | | ib_h9 | ib_h8 | ib_h7 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | ib_h6 | ib_h5 | ib_h4 | ib_h3 | ib_h2 | ib_h1 | ib_h0 | ib_l5 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | ib_l4 | ib_l3 | ib_l2 | ib_l1 | ib_l0 | + * * * Ideal output ADC codes corresponding to injected input voltages * during manufacturing is: @@ -519,38 +723,116 @@ static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) * vbat_low: Vin = 2380mV / ADC ideal code = 33 */ - /* Calculate gain and offset for VMAIN if all reads succeeded */ - if (!(ret[0] < 0 || ret[1] < 0 || ret[2] < 0)) { - vmain_high = (((gpadc_cal[0] & 0x03) << 8) | - ((gpadc_cal[1] & 0x3F) << 2) | - ((gpadc_cal[2] & 0xC0) >> 6)); + if (is_ab8540(ab8500)) { + /* Calculate gain and offset for VMAIN if all reads succeeded*/ + if (!(ret[1] < 0 || ret[2] < 0)) { + vmain_high = (((gpadc_cal[1] & 0xFF) << 2) | + ((gpadc_cal[2] & 0xC0) >> 6)); + vmain_low = ((gpadc_cal[2] & 0x3E) >> 1); + + gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_hi = + (u16)vmain_high; + gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_lo = + (u16)vmain_low; + + gpadc->cal_data[ADC_INPUT_VMAIN].gain = CALIB_SCALE * + (19500 - 315) / (vmain_high - vmain_low); + gpadc->cal_data[ADC_INPUT_VMAIN].offset = CALIB_SCALE * + 19500 - (CALIB_SCALE * (19500 - 315) / + (vmain_high - vmain_low)) * vmain_high; + } else { + gpadc->cal_data[ADC_INPUT_VMAIN].gain = 0; + } - vmain_low = ((gpadc_cal[2] & 0x3E) >> 1); + /* Read IBAT calibration Data */ + for (i = 0; i < ARRAY_SIZE(otp4_cal_regs); i++) { + ret_otp4[i] = abx500_get_register_interruptible( + gpadc->dev, AB8500_OTP_EMUL, + otp4_cal_regs[i], &gpadc_otp4[i]); + if (ret_otp4[i] < 0) + dev_err(gpadc->dev, + "%s: read otp4 reg 0x%02x failed\n", + __func__, otp4_cal_regs[i]); + } - gpadc->cal_data[ADC_INPUT_VMAIN].gain = CALIB_SCALE * - (19500 - 315) / (vmain_high - vmain_low); + /* Calculate gain and offset for IBAT if all reads succeeded */ + if (!(ret_otp4[0] < 0 || ret_otp4[1] < 0 || ret_otp4[2] < 0)) { + ibat_high = (((gpadc_otp4[0] & 0x07) << 7) | + ((gpadc_otp4[1] & 0xFE) >> 1)); + ibat_low = (((gpadc_otp4[1] & 0x01) << 5) | + ((gpadc_otp4[2] & 0xF8) >> 3)); + + gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_hi = + (u16)ibat_high; + gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_lo = + (u16)ibat_low; + + V_gain = ((IBAT_VDROP_H - IBAT_VDROP_L) + << CALIB_SHIFT_IBAT) / (ibat_high - ibat_low); + + V_offset = (IBAT_VDROP_H << CALIB_SHIFT_IBAT) - + (((IBAT_VDROP_H - IBAT_VDROP_L) << + CALIB_SHIFT_IBAT) / (ibat_high - ibat_low)) + * ibat_high; + /* + * Result obtained is in mV (at a scale factor), + * we need to calculate gain and offset to get mA + */ + V2A_gain = (ADC_CH_IBAT_MAX - ADC_CH_IBAT_MIN)/ + (ADC_CH_IBAT_MAX_V - ADC_CH_IBAT_MIN_V); + V2A_offset = ((ADC_CH_IBAT_MAX_V * ADC_CH_IBAT_MIN - + ADC_CH_IBAT_MAX * ADC_CH_IBAT_MIN_V) + << CALIB_SHIFT_IBAT) + / (ADC_CH_IBAT_MAX_V - ADC_CH_IBAT_MIN_V); + + gpadc->cal_data[ADC_INPUT_IBAT].gain = V_gain * V2A_gain; + gpadc->cal_data[ADC_INPUT_IBAT].offset = V_offset * + V2A_gain + V2A_offset; + } else { + gpadc->cal_data[ADC_INPUT_IBAT].gain = 0; + } - gpadc->cal_data[ADC_INPUT_VMAIN].offset = CALIB_SCALE * 19500 - - (CALIB_SCALE * (19500 - 315) / - (vmain_high - vmain_low)) * vmain_high; + dev_dbg(gpadc->dev, "IBAT gain %llu offset %llu\n", + gpadc->cal_data[ADC_INPUT_IBAT].gain, + gpadc->cal_data[ADC_INPUT_IBAT].offset); } else { - gpadc->cal_data[ADC_INPUT_VMAIN].gain = 0; + /* Calculate gain and offset for VMAIN if all reads succeeded */ + if (!(ret[0] < 0 || ret[1] < 0 || ret[2] < 0)) { + vmain_high = (((gpadc_cal[0] & 0x03) << 8) | + ((gpadc_cal[1] & 0x3F) << 2) | + ((gpadc_cal[2] & 0xC0) >> 6)); + vmain_low = ((gpadc_cal[2] & 0x3E) >> 1); + + gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_hi = + (u16)vmain_high; + gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_lo = + (u16)vmain_low; + + gpadc->cal_data[ADC_INPUT_VMAIN].gain = CALIB_SCALE * + (19500 - 315) / (vmain_high - vmain_low); + + gpadc->cal_data[ADC_INPUT_VMAIN].offset = CALIB_SCALE * + 19500 - (CALIB_SCALE * (19500 - 315) / + (vmain_high - vmain_low)) * vmain_high; + } else { + gpadc->cal_data[ADC_INPUT_VMAIN].gain = 0; + } } /* Calculate gain and offset for BTEMP if all reads succeeded */ if (!(ret[2] < 0 || ret[3] < 0 || ret[4] < 0)) { btemp_high = (((gpadc_cal[2] & 0x01) << 9) | - (gpadc_cal[3] << 1) | - ((gpadc_cal[4] & 0x80) >> 7)); - + (gpadc_cal[3] << 1) | ((gpadc_cal[4] & 0x80) >> 7)); btemp_low = ((gpadc_cal[4] & 0x7C) >> 2); + gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_hi = (u16)btemp_high; + gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_lo = (u16)btemp_low; + gpadc->cal_data[ADC_INPUT_BTEMP].gain = CALIB_SCALE * (1300 - 21) / (btemp_high - btemp_low); - gpadc->cal_data[ADC_INPUT_BTEMP].offset = CALIB_SCALE * 1300 - - (CALIB_SCALE * (1300 - 21) / - (btemp_high - btemp_low)) * btemp_high; + (CALIB_SCALE * (1300 - 21) / (btemp_high - btemp_low)) + * btemp_high; } else { gpadc->cal_data[ADC_INPUT_BTEMP].gain = 0; } @@ -560,9 +842,11 @@ static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) vbat_high = (((gpadc_cal[4] & 0x03) << 8) | gpadc_cal[5]); vbat_low = ((gpadc_cal[6] & 0xFC) >> 2); + gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_hi = (u16)vbat_high; + gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_lo = (u16)vbat_low; + gpadc->cal_data[ADC_INPUT_VBAT].gain = CALIB_SCALE * (4700 - 2380) / (vbat_high - vbat_low); - gpadc->cal_data[ADC_INPUT_VBAT].offset = CALIB_SCALE * 4700 - (CALIB_SCALE * (4700 - 2380) / (vbat_high - vbat_low)) * vbat_high; @@ -608,6 +892,31 @@ static int ab8500_gpadc_runtime_idle(struct device *dev) return 0; } +static int ab8500_gpadc_suspend(struct device *dev) +{ + struct ab8500_gpadc *gpadc = dev_get_drvdata(dev); + + mutex_lock(&gpadc->ab8500_gpadc_lock); + + pm_runtime_get_sync(dev); + + regulator_disable(gpadc->regu); + return 0; +} + +static int ab8500_gpadc_resume(struct device *dev) +{ + struct ab8500_gpadc *gpadc = dev_get_drvdata(dev); + + regulator_enable(gpadc->regu); + + pm_runtime_mark_last_busy(gpadc->dev); + pm_runtime_put_autosuspend(gpadc->dev); + + mutex_unlock(&gpadc->ab8500_gpadc_lock); + return 0; +} + static int ab8500_gpadc_probe(struct platform_device *pdev) { int ret = 0; @@ -619,13 +928,13 @@ static int ab8500_gpadc_probe(struct platform_device *pdev) return -ENOMEM; } - gpadc->irq = platform_get_irq_byname(pdev, "SW_CONV_END"); - if (gpadc->irq < 0) { - dev_err(&pdev->dev, "failed to get platform irq-%d\n", - gpadc->irq); - ret = gpadc->irq; - goto fail; - } + gpadc->irq_sw = platform_get_irq_byname(pdev, "SW_CONV_END"); + if (gpadc->irq_sw < 0) + dev_err(gpadc->dev, "failed to get platform sw_conv_end irq\n"); + + gpadc->irq_hw = platform_get_irq_byname(pdev, "HW_CONV_END"); + if (gpadc->irq_hw < 0) + dev_err(gpadc->dev, "failed to get platform hw_conv_end irq\n"); gpadc->dev = &pdev->dev; gpadc->parent = dev_get_drvdata(pdev->dev.parent); @@ -634,15 +943,31 @@ static int ab8500_gpadc_probe(struct platform_device *pdev) /* Initialize completion used to notify completion of conversion */ init_completion(&gpadc->ab8500_gpadc_complete); - /* Register interrupt - SwAdcComplete */ - ret = request_threaded_irq(gpadc->irq, NULL, - ab8500_bm_gpswadcconvend_handler, - IRQF_ONESHOT | IRQF_NO_SUSPEND | IRQF_SHARED, - "ab8500-gpadc", gpadc); - if (ret < 0) { - dev_err(gpadc->dev, "Failed to register interrupt, irq: %d\n", - gpadc->irq); - goto fail; + /* Register interrupts */ + if (gpadc->irq_sw >= 0) { + ret = request_threaded_irq(gpadc->irq_sw, NULL, + ab8500_bm_gpadcconvend_handler, + IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc-sw", + gpadc); + if (ret < 0) { + dev_err(gpadc->dev, + "Failed to register interrupt irq: %d\n", + gpadc->irq_sw); + goto fail; + } + } + + if (gpadc->irq_hw >= 0) { + ret = request_threaded_irq(gpadc->irq_hw, NULL, + ab8500_bm_gpadcconvend_handler, + IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc-hw", + gpadc); + if (ret < 0) { + dev_err(gpadc->dev, + "Failed to register interrupt irq: %d\n", + gpadc->irq_hw); + goto fail_irq; + } } /* VTVout LDO used to power up ab8500-GPADC */ @@ -669,11 +994,13 @@ static int ab8500_gpadc_probe(struct platform_device *pdev) ab8500_gpadc_read_calibration_data(gpadc); list_add_tail(&gpadc->node, &ab8500_gpadc_list); dev_dbg(gpadc->dev, "probe success\n"); + return 0; fail_enable: fail_irq: - free_irq(gpadc->irq, gpadc); + free_irq(gpadc->irq_sw, gpadc); + free_irq(gpadc->irq_hw, gpadc); fail: kfree(gpadc); gpadc = NULL; @@ -687,7 +1014,10 @@ static int ab8500_gpadc_remove(struct platform_device *pdev) /* remove this gpadc entry from the list */ list_del(&gpadc->node); /* remove interrupt - completion of Sw ADC conversion */ - free_irq(gpadc->irq, gpadc); + if (gpadc->irq_sw >= 0) + free_irq(gpadc->irq_sw, gpadc); + if (gpadc->irq_hw >= 0) + free_irq(gpadc->irq_hw, gpadc); pm_runtime_get_sync(gpadc->dev); pm_runtime_disable(gpadc->dev); @@ -707,6 +1037,9 @@ static const struct dev_pm_ops ab8500_gpadc_pm_ops = { SET_RUNTIME_PM_OPS(ab8500_gpadc_runtime_suspend, ab8500_gpadc_runtime_resume, ab8500_gpadc_runtime_idle) + SET_SYSTEM_SLEEP_PM_OPS(ab8500_gpadc_suspend, + ab8500_gpadc_resume) + }; static struct platform_driver ab8500_gpadc_driver = { @@ -729,10 +1062,30 @@ static void __exit ab8500_gpadc_exit(void) platform_driver_unregister(&ab8500_gpadc_driver); } +/** + * ab8540_gpadc_get_otp() - returns OTP values + * + */ +void ab8540_gpadc_get_otp(struct ab8500_gpadc *gpadc, + u16 *vmain_l, u16 *vmain_h, u16 *btemp_l, u16 *btemp_h, + u16 *vbat_l, u16 *vbat_h, u16 *ibat_l, u16 *ibat_h) +{ + *vmain_l = gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_lo; + *vmain_h = gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_hi; + *btemp_l = gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_lo; + *btemp_h = gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_hi; + *vbat_l = gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_lo; + *vbat_h = gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_hi; + *ibat_l = gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_lo; + *ibat_h = gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_hi; + return ; +} + subsys_initcall_sync(ab8500_gpadc_init); module_exit(ab8500_gpadc_exit); MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Arun R Murthy, Daniel Willerud, Johan Palsson"); +MODULE_AUTHOR("Arun R Murthy, Daniel Willerud, Johan Palsson," + "M'boumba Cedric Madianga"); MODULE_ALIAS("platform:ab8500_gpadc"); MODULE_DESCRIPTION("AB8500 GPADC driver"); diff --git a/drivers/mfd/ab8500-sysctrl.c b/drivers/mfd/ab8500-sysctrl.c index 108fd86552f0..fbca1ced49fa 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -15,19 +15,30 @@ #include <linux/mfd/abx500/ab8500.h> #include <linux/mfd/abx500/ab8500-sysctrl.h> +/* RtcCtrl bits */ +#define AB8500_ALARM_MIN_LOW 0x08 +#define AB8500_ALARM_MIN_MID 0x09 +#define RTC_CTRL 0x0B +#define RTC_ALARM_ENABLE 0x4 + static struct device *sysctrl_dev; void ab8500_power_off(void) { sigset_t old; sigset_t all; - static char *pss[] = {"ab8500_ac", "ab8500_usb"}; + static char *pss[] = {"ab8500_ac", "pm2301", "ab8500_usb"}; int i; bool charger_present = false; union power_supply_propval val; struct power_supply *psy; int ret; + if (sysctrl_dev == NULL) { + pr_err("%s: sysctrl not initialized\n", __func__); + return; + } + /* * If we have a charger connected and we're powering off, * reboot into charge-only mode. @@ -74,6 +85,63 @@ shutdown: } } +/* + * Use the AB WD to reset the platform. It will perform a hard + * reset instead of a soft reset. Write the reset reason to + * the AB before reset, which can be read upon restart. + */ +void ab8500_restart(char mode, const char *cmd) +{ + struct ab8500_platform_data *plat; + struct ab8500_sysctrl_platform_data *pdata; + u16 reason = 0; + u8 val; + + if (sysctrl_dev == NULL) { + pr_err("%s: sysctrl not initialized\n", __func__); + return; + } + + plat = dev_get_platdata(sysctrl_dev->parent); + pdata = plat->sysctrl; + if (pdata->reboot_reason_code) + reason = pdata->reboot_reason_code(cmd); + else + pr_warn("[%s] No reboot reason set. Default reason %d\n", + __func__, reason); + + /* + * Disable RTC alarm, just a precaution so that no alarm + * is running when WD reset is executed. + */ + abx500_get_register_interruptible(sysctrl_dev, AB8500_RTC, + RTC_CTRL , &val); + abx500_set_register_interruptible(sysctrl_dev, AB8500_RTC, + RTC_CTRL , (val & ~RTC_ALARM_ENABLE)); + + /* + * Android is not using the RTC alarm registers during reboot + * so we borrow them for writing the reason of reset + */ + + /* reason[8 LSB] */ + val = reason & 0xFF; + abx500_set_register_interruptible(sysctrl_dev, AB8500_RTC, + AB8500_ALARM_MIN_LOW , val); + + /* reason[8 MSB] */ + val = (reason>>8) & 0xFF; + abx500_set_register_interruptible(sysctrl_dev, AB8500_RTC, + AB8500_ALARM_MIN_MID , val); + + /* Setting WD timeout to 0 */ + ab8500_sysctrl_write(AB8500_MAINWDOGTIMER, 0xFF, 0x0); + + /* Setting the parameters to AB8500 WD*/ + ab8500_sysctrl_write(AB8500_MAINWDOGCTRL, 0xFF, (AB8500_ENABLE_WD | + AB8500_WD_RESTART_ON_EXPIRE | AB8500_KICK_WD)); +} + static inline bool valid_bank(u8 bank) { return ((bank == AB8500_SYS_CTRL1_BLOCK) || @@ -85,7 +153,7 @@ int ab8500_sysctrl_read(u16 reg, u8 *value) u8 bank; if (sysctrl_dev == NULL) - return -EAGAIN; + return -EINVAL; bank = (reg >> 8); if (!valid_bank(bank)) @@ -101,7 +169,7 @@ int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value) u8 bank; if (sysctrl_dev == NULL) - return -EAGAIN; + return -EINVAL; bank = (reg >> 8); if (!valid_bank(bank)) @@ -114,28 +182,36 @@ EXPORT_SYMBOL(ab8500_sysctrl_write); static int ab8500_sysctrl_probe(struct platform_device *pdev) { + struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); struct ab8500_platform_data *plat; struct ab8500_sysctrl_platform_data *pdata; - sysctrl_dev = &pdev->dev; plat = dev_get_platdata(pdev->dev.parent); + + if (!(plat && plat->sysctrl)) + return -EINVAL; + if (plat->pm_power_off) pm_power_off = ab8500_power_off; pdata = plat->sysctrl; if (pdata) { - int ret, i, j; + int last, ret, i, j; + + if (is_ab8505(ab8500)) + last = AB8500_SYSCLKREQ4RFCLKBUF; + else + last = AB8500_SYSCLKREQ8RFCLKBUF; - for (i = AB8500_SYSCLKREQ1RFCLKBUF; - i <= AB8500_SYSCLKREQ8RFCLKBUF; i++) { + for (i = AB8500_SYSCLKREQ1RFCLKBUF; i <= last; i++) { j = i - AB8500_SYSCLKREQ1RFCLKBUF; ret = ab8500_sysctrl_write(i, 0xff, - pdata->initial_req_buf_config[j]); + pdata->initial_req_buf_config[j]); dev_dbg(&pdev->dev, - "Setting SysClkReq%dRfClkBuf 0x%X\n", - j + 1, - pdata->initial_req_buf_config[j]); + "Setting SysClkReq%dRfClkBuf 0x%X\n", + j + 1, + pdata->initial_req_buf_config[j]); if (ret < 0) { dev_err(&pdev->dev, "unable to set sysClkReq%dRfClkBuf: " @@ -166,7 +242,7 @@ static int __init ab8500_sysctrl_init(void) { return platform_driver_register(&ab8500_sysctrl_driver); } -subsys_initcall(ab8500_sysctrl_init); +arch_initcall(ab8500_sysctrl_init); MODULE_AUTHOR("Mattias Nilsson <mattias.i.nilsson@stericsson.com"); MODULE_DESCRIPTION("AB8500 system control driver"); diff --git a/drivers/mfd/adp5520.c b/drivers/mfd/adp5520.c index 210dd038bb5a..0d2eba023439 100644 --- a/drivers/mfd/adp5520.c +++ b/drivers/mfd/adp5520.c @@ -36,6 +36,7 @@ struct adp5520_chip { struct blocking_notifier_head notifier_list; int irq; unsigned long id; + uint8_t mode; }; static int __adp5520_read(struct i2c_client *client, @@ -326,7 +327,10 @@ static int adp5520_suspend(struct device *dev) struct i2c_client *client = to_i2c_client(dev); struct adp5520_chip *chip = dev_get_drvdata(&client->dev); - adp5520_clr_bits(chip->dev, ADP5520_MODE_STATUS, ADP5520_nSTNBY); + adp5520_read(chip->dev, ADP5520_MODE_STATUS, &chip->mode); + /* All other bits are W1C */ + chip->mode &= ADP5520_BL_EN | ADP5520_DIM_EN | ADP5520_nSTNBY; + adp5520_write(chip->dev, ADP5520_MODE_STATUS, 0); return 0; } @@ -335,7 +339,7 @@ static int adp5520_resume(struct device *dev) struct i2c_client *client = to_i2c_client(dev); struct adp5520_chip *chip = dev_get_drvdata(&client->dev); - adp5520_set_bits(chip->dev, ADP5520_MODE_STATUS, ADP5520_nSTNBY); + adp5520_write(chip->dev, ADP5520_MODE_STATUS, chip->mode); return 0; } #endif @@ -360,17 +364,7 @@ static struct i2c_driver adp5520_driver = { .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_i2c_driver(adp5520_driver); MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); MODULE_DESCRIPTION("ADP5520(01) PMIC-MFD Driver"); diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index b562c7bf8a46..6ab03043fd60 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -39,11 +39,21 @@ int arizona_clk32k_enable(struct arizona *arizona) arizona->clk32k_ref++; - if (arizona->clk32k_ref == 1) + if (arizona->clk32k_ref == 1) { + switch (arizona->pdata.clk32k_src) { + case ARIZONA_32KZ_MCLK1: + ret = pm_runtime_get_sync(arizona->dev); + if (ret != 0) + goto out; + break; + } + ret = regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1, ARIZONA_CLK_32K_ENA, ARIZONA_CLK_32K_ENA); + } +out: if (ret != 0) arizona->clk32k_ref--; @@ -63,10 +73,17 @@ int arizona_clk32k_disable(struct arizona *arizona) arizona->clk32k_ref--; - if (arizona->clk32k_ref == 0) + if (arizona->clk32k_ref == 0) { regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1, ARIZONA_CLK_32K_ENA, 0); + switch (arizona->pdata.clk32k_src) { + case ARIZONA_32KZ_MCLK1: + pm_runtime_put_sync(arizona->dev); + break; + } + } + mutex_unlock(&arizona->clk_lock); return ret; @@ -179,42 +196,134 @@ static irqreturn_t arizona_overclocked(int irq, void *data) return IRQ_HANDLED; } -static int arizona_wait_for_boot(struct arizona *arizona) +static int arizona_poll_reg(struct arizona *arizona, + int timeout, unsigned int reg, + unsigned int mask, unsigned int target) { - unsigned int reg; + unsigned int val = 0; int ret, i; + for (i = 0; i < timeout; i++) { + ret = regmap_read(arizona->regmap, reg, &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read reg %u: %d\n", + reg, ret); + continue; + } + + if ((val & mask) == target) + return 0; + + msleep(1); + } + + dev_err(arizona->dev, "Polling reg %u timed out: %x\n", reg, val); + return -ETIMEDOUT; +} + +static int arizona_wait_for_boot(struct arizona *arizona) +{ + int ret; + /* * We can't use an interrupt as we need to runtime resume to do so, * we won't race with the interrupt handler as it'll be blocked on * runtime resume. */ - for (i = 0; i < 5; i++) { - msleep(1); + ret = arizona_poll_reg(arizona, 5, ARIZONA_INTERRUPT_RAW_STATUS_5, + ARIZONA_BOOT_DONE_STS, ARIZONA_BOOT_DONE_STS); - ret = regmap_read(arizona->regmap, - ARIZONA_INTERRUPT_RAW_STATUS_5, ®); - if (ret != 0) { - dev_err(arizona->dev, "Failed to read boot state: %d\n", - ret); - continue; - } + if (!ret) + regmap_write(arizona->regmap, ARIZONA_INTERRUPT_STATUS_5, + ARIZONA_BOOT_DONE_STS); - if (reg & ARIZONA_BOOT_DONE_STS) - break; + pm_runtime_mark_last_busy(arizona->dev); + + return ret; +} + +static int arizona_apply_hardware_patch(struct arizona* arizona) +{ + unsigned int fll, sysclk; + int ret, err; + + regcache_cache_bypass(arizona->regmap, true); + + /* Cache existing FLL and SYSCLK settings */ + ret = regmap_read(arizona->regmap, ARIZONA_FLL1_CONTROL_1, &fll); + if (ret != 0) { + dev_err(arizona->dev, "Failed to cache FLL settings: %d\n", + ret); + return ret; + } + ret = regmap_read(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, &sysclk); + if (ret != 0) { + dev_err(arizona->dev, "Failed to cache SYSCLK settings: %d\n", + ret); + return ret; } - if (reg & ARIZONA_BOOT_DONE_STS) { - regmap_write(arizona->regmap, ARIZONA_INTERRUPT_STATUS_5, - ARIZONA_BOOT_DONE_STS); - } else { - dev_err(arizona->dev, "Device boot timed out: %x\n", reg); - return -ETIMEDOUT; + /* Start up SYSCLK using the FLL in free running mode */ + ret = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, + ARIZONA_FLL1_ENA | ARIZONA_FLL1_FREERUN); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to start FLL in freerunning mode: %d\n", + ret); + return ret; + } + ret = arizona_poll_reg(arizona, 25, ARIZONA_INTERRUPT_RAW_STATUS_5, + ARIZONA_FLL1_CLOCK_OK_STS, + ARIZONA_FLL1_CLOCK_OK_STS); + if (ret != 0) { + ret = -ETIMEDOUT; + goto err_fll; } - pm_runtime_mark_last_busy(arizona->dev); + ret = regmap_write(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, 0x0144); + if (ret != 0) { + dev_err(arizona->dev, "Failed to start SYSCLK: %d\n", ret); + goto err_fll; + } - return 0; + /* Start the write sequencer and wait for it to finish */ + ret = regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_0, + ARIZONA_WSEQ_ENA | ARIZONA_WSEQ_START | 160); + if (ret != 0) { + dev_err(arizona->dev, "Failed to start write sequencer: %d\n", + ret); + goto err_sysclk; + } + ret = arizona_poll_reg(arizona, 5, ARIZONA_WRITE_SEQUENCER_CTRL_1, + ARIZONA_WSEQ_BUSY, 0); + if (ret != 0) { + regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_0, + ARIZONA_WSEQ_ABORT); + ret = -ETIMEDOUT; + } + +err_sysclk: + err = regmap_write(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, sysclk); + if (err != 0) { + dev_err(arizona->dev, + "Failed to re-apply old SYSCLK settings: %d\n", + err); + } + +err_fll: + err = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, fll); + if (err != 0) { + dev_err(arizona->dev, + "Failed to re-apply old FLL settings: %d\n", + err); + } + + regcache_cache_bypass(arizona->regmap, false); + + if (ret != 0) + return ret; + else + return err; } #ifdef CONFIG_PM_RUNTIME @@ -233,20 +342,44 @@ static int arizona_runtime_resume(struct device *dev) regcache_cache_only(arizona->regmap, false); - ret = arizona_wait_for_boot(arizona); - if (ret != 0) { - regulator_disable(arizona->dcvdd); - return ret; + switch (arizona->type) { + case WM5102: + ret = wm5102_patch(arizona); + if (ret != 0) { + dev_err(arizona->dev, "Failed to apply patch: %d\n", + ret); + goto err; + } + + ret = arizona_apply_hardware_patch(arizona); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to apply hardware patch: %d\n", + ret); + goto err; + } + break; + default: + ret = arizona_wait_for_boot(arizona); + if (ret != 0) { + goto err; + } + + break; } ret = regcache_sync(arizona->regmap); if (ret != 0) { dev_err(arizona->dev, "Failed to restore register cache\n"); - regulator_disable(arizona->dcvdd); - return ret; + goto err; } return 0; + +err: + regcache_cache_only(arizona->regmap, true); + regulator_disable(arizona->dcvdd); + return ret; } static int arizona_runtime_suspend(struct device *dev) @@ -371,6 +504,17 @@ int arizona_dev_init(struct arizona *arizona) goto err_early; } + if (arizona->pdata.reset) { + /* Start out with /RESET low to put the chip into reset */ + ret = gpio_request_one(arizona->pdata.reset, + GPIOF_DIR_OUT | GPIOF_INIT_LOW, + "arizona /RESET"); + if (ret != 0) { + dev_err(dev, "Failed to request /RESET: %d\n", ret); + goto err_early; + } + } + ret = regulator_bulk_enable(arizona->num_core_supplies, arizona->core_supplies); if (ret != 0) { @@ -386,16 +530,8 @@ int arizona_dev_init(struct arizona *arizona) } if (arizona->pdata.reset) { - /* Start out with /RESET low to put the chip into reset */ - ret = gpio_request_one(arizona->pdata.reset, - GPIOF_DIR_OUT | GPIOF_INIT_LOW, - "arizona /RESET"); - if (ret != 0) { - dev_err(dev, "Failed to request /RESET: %d\n", ret); - goto err_dcvdd; - } - gpio_set_value_cansleep(arizona->pdata.reset, 1); + msleep(1); } regcache_cache_only(arizona->regmap, false); @@ -424,6 +560,7 @@ int arizona_dev_init(struct arizona *arizona) arizona->type = WM5102; } apply_patch = wm5102_patch; + arizona->rev &= 0x7; break; #endif #ifdef CONFIG_MFD_WM5110 @@ -454,6 +591,8 @@ int arizona_dev_init(struct arizona *arizona) goto err_reset; } + msleep(1); + ret = regcache_sync(arizona->regmap); if (ret != 0) { dev_err(dev, "Failed to sync device: %d\n", ret); @@ -461,10 +600,24 @@ int arizona_dev_init(struct arizona *arizona) } } - ret = arizona_wait_for_boot(arizona); - if (ret != 0) { - dev_err(arizona->dev, "Device failed initial boot: %d\n", ret); - goto err_reset; + switch (arizona->type) { + case WM5102: + ret = regmap_read(arizona->regmap, 0x19, &val); + if (ret != 0) + dev_err(dev, + "Failed to check write sequencer state: %d\n", + ret); + else if (val & 0x01) + break; + /* Fall through */ + default: + ret = arizona_wait_for_boot(arizona); + if (ret != 0) { + dev_err(arizona->dev, + "Device failed initial boot: %d\n", ret); + goto err_reset; + } + break; } if (apply_patch) { @@ -474,6 +627,20 @@ int arizona_dev_init(struct arizona *arizona) ret); goto err_reset; } + + switch (arizona->type) { + case WM5102: + ret = arizona_apply_hardware_patch(arizona); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to apply hardware patch: %d\n", + ret); + goto err_reset; + } + break; + default: + break; + } } for (i = 0; i < ARRAY_SIZE(arizona->pdata.gpio_defaults); i++) { @@ -498,6 +665,7 @@ int arizona_dev_init(struct arizona *arizona) regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1, ARIZONA_CLK_32K_SRC_MASK, arizona->pdata.clk32k_src - 1); + arizona_clk32k_enable(arizona); break; case ARIZONA_32KZ_NONE: regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1, @@ -511,10 +679,16 @@ int arizona_dev_init(struct arizona *arizona) } for (i = 0; i < ARIZONA_MAX_MICBIAS; i++) { - if (!arizona->pdata.micbias[i].mV) + if (!arizona->pdata.micbias[i].mV && + !arizona->pdata.micbias[i].bypass) continue; + /* Apply default for bypass mode */ + if (!arizona->pdata.micbias[i].mV) + arizona->pdata.micbias[i].mV = 2800; + val = (arizona->pdata.micbias[i].mV - 1500) / 100; + val <<= ARIZONA_MICB1_LVL_SHIFT; if (arizona->pdata.micbias[i].ext_cap) @@ -526,10 +700,14 @@ int arizona_dev_init(struct arizona *arizona) if (arizona->pdata.micbias[i].fast_start) val |= ARIZONA_MICB1_RATE; + if (arizona->pdata.micbias[i].bypass) + val |= ARIZONA_MICB1_BYPASS; + regmap_update_bits(arizona->regmap, ARIZONA_MIC_BIAS_CTRL_1 + i, ARIZONA_MICB1_LVL_MASK | ARIZONA_MICB1_DISCH | + ARIZONA_MICB1_BYPASS | ARIZONA_MICB1_RATE, val); } @@ -610,10 +788,9 @@ err_irq: arizona_irq_exit(arizona); err_reset: if (arizona->pdata.reset) { - gpio_set_value_cansleep(arizona->pdata.reset, 1); + gpio_set_value_cansleep(arizona->pdata.reset, 0); gpio_free(arizona->pdata.reset); } -err_dcvdd: regulator_disable(arizona->dcvdd); err_enable: regulator_bulk_disable(arizona->num_core_supplies, diff --git a/drivers/mfd/arizona-irq.c b/drivers/mfd/arizona-irq.c index 2bec5f0db3ee..64cd9b6dac92 100644 --- a/drivers/mfd/arizona-irq.c +++ b/drivers/mfd/arizona-irq.c @@ -94,6 +94,7 @@ static irqreturn_t arizona_ctrlif_err(int irq, void *data) static irqreturn_t arizona_irq_thread(int irq, void *data) { struct arizona *arizona = data; + bool poll; unsigned int val; int ret; @@ -103,20 +104,39 @@ static irqreturn_t arizona_irq_thread(int irq, void *data) return IRQ_NONE; } - /* Always handle the AoD domain */ - handle_nested_irq(irq_find_mapping(arizona->virq, 0)); + do { + poll = false; + + /* Always handle the AoD domain */ + handle_nested_irq(irq_find_mapping(arizona->virq, 0)); + + /* + * Check if one of the main interrupts is asserted and only + * check that domain if it is. + */ + ret = regmap_read(arizona->regmap, ARIZONA_IRQ_PIN_STATUS, + &val); + if (ret == 0 && val & ARIZONA_IRQ1_STS) { + handle_nested_irq(irq_find_mapping(arizona->virq, 1)); + } else if (ret != 0) { + dev_err(arizona->dev, + "Failed to read main IRQ status: %d\n", ret); + } - /* - * Check if one of the main interrupts is asserted and only - * check that domain if it is. - */ - ret = regmap_read(arizona->regmap, ARIZONA_IRQ_PIN_STATUS, &val); - if (ret == 0 && val & ARIZONA_IRQ1_STS) { - handle_nested_irq(irq_find_mapping(arizona->virq, 1)); - } else if (ret != 0) { - dev_err(arizona->dev, "Failed to read main IRQ status: %d\n", - ret); - } + /* + * Poll the IRQ pin status to see if we're really done + * if the interrupt controller can't do it for us. + */ + if (!arizona->pdata.irq_gpio) { + break; + } else if (arizona->pdata.irq_flags & IRQF_TRIGGER_RISING && + gpio_get_value_cansleep(arizona->pdata.irq_gpio)) { + poll = true; + } else if (arizona->pdata.irq_flags & IRQF_TRIGGER_FALLING && + !gpio_get_value_cansleep(arizona->pdata.irq_gpio)) { + poll = true; + } + } while (poll); pm_runtime_mark_last_busy(arizona->dev); pm_runtime_put_autosuspend(arizona->dev); @@ -169,6 +189,7 @@ int arizona_irq_init(struct arizona *arizona) int ret, i; const struct regmap_irq_chip *aod, *irq; bool ctrlif_error = true; + struct irq_data *irq_data; switch (arizona->type) { #ifdef CONFIG_MFD_WM5102 @@ -192,7 +213,36 @@ int arizona_irq_init(struct arizona *arizona) return -EINVAL; } - if (arizona->pdata.irq_active_high) { + /* Disable all wake sources by default */ + regmap_write(arizona->regmap, ARIZONA_WAKE_CONTROL, 0); + + /* Read the flags from the interrupt controller if not specified */ + if (!arizona->pdata.irq_flags) { + irq_data = irq_get_irq_data(arizona->irq); + if (!irq_data) { + dev_err(arizona->dev, "Invalid IRQ: %d\n", + arizona->irq); + return -EINVAL; + } + + arizona->pdata.irq_flags = irqd_get_trigger_type(irq_data); + switch (arizona->pdata.irq_flags) { + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_HIGH: + case IRQF_TRIGGER_RISING: + case IRQF_TRIGGER_FALLING: + break; + + case IRQ_TYPE_NONE: + default: + /* Device default */ + arizona->pdata.irq_flags = IRQF_TRIGGER_LOW; + break; + } + } + + if (arizona->pdata.irq_flags & (IRQF_TRIGGER_HIGH | + IRQF_TRIGGER_RISING)) { ret = regmap_update_bits(arizona->regmap, ARIZONA_IRQ_CTRL_1, ARIZONA_IRQ_POL, 0); if (ret != 0) { @@ -200,12 +250,10 @@ int arizona_irq_init(struct arizona *arizona) ret); goto err; } - - flags |= IRQF_TRIGGER_HIGH; - } else { - flags |= IRQF_TRIGGER_LOW; } + flags |= arizona->pdata.irq_flags; + /* Allocate a virtual IRQ domain to distribute to the regmap domains */ arizona->virq = irq_domain_add_linear(NULL, 2, &arizona_domain_ops, arizona); @@ -257,11 +305,31 @@ int arizona_irq_init(struct arizona *arizona) } } + /* Used to emulate edge trigger and to work around broken pinmux */ + if (arizona->pdata.irq_gpio) { + if (gpio_to_irq(arizona->pdata.irq_gpio) != arizona->irq) { + dev_warn(arizona->dev, "IRQ %d is not GPIO %d (%d)\n", + arizona->irq, arizona->pdata.irq_gpio, + gpio_to_irq(arizona->pdata.irq_gpio)); + arizona->irq = gpio_to_irq(arizona->pdata.irq_gpio); + } + + ret = devm_gpio_request_one(arizona->dev, + arizona->pdata.irq_gpio, + GPIOF_IN, "arizona IRQ"); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to request IRQ GPIO %d:: %d\n", + arizona->pdata.irq_gpio, ret); + arizona->pdata.irq_gpio = 0; + } + } + ret = request_threaded_irq(arizona->irq, NULL, arizona_irq_thread, flags, "arizona", arizona); if (ret != 0) { - dev_err(arizona->dev, "Failed to request IRQ %d: %d\n", + dev_err(arizona->dev, "Failed to request primary IRQ %d: %d\n", arizona->irq, ret); goto err_main_irq; } diff --git a/drivers/mfd/arizona-spi.c b/drivers/mfd/arizona-spi.c index 1b9fdd698b03..b57e642d2b4a 100644 --- a/drivers/mfd/arizona-spi.c +++ b/drivers/mfd/arizona-spi.c @@ -67,7 +67,7 @@ static int arizona_spi_probe(struct spi_device *spi) static int arizona_spi_remove(struct spi_device *spi) { - struct arizona *arizona = dev_get_drvdata(&spi->dev); + struct arizona *arizona = spi_get_drvdata(spi); arizona_dev_exit(arizona); return 0; } diff --git a/drivers/mfd/as3711.c b/drivers/mfd/as3711.c index e994c9691124..01e414162702 100644 --- a/drivers/mfd/as3711.c +++ b/drivers/mfd/as3711.c @@ -112,16 +112,34 @@ static const struct regmap_config as3711_regmap_config = { .cache_type = REGCACHE_RBTREE, }; +#ifdef CONFIG_OF +static struct of_device_id as3711_of_match[] = { + {.compatible = "ams,as3711",}, + {} +}; +MODULE_DEVICE_TABLE(of, as3711_of_match); +#endif + static int as3711_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct as3711 *as3711; - struct as3711_platform_data *pdata = client->dev.platform_data; + struct as3711_platform_data *pdata; unsigned int id1, id2; int ret; - if (!pdata) - dev_dbg(&client->dev, "Platform data not found\n"); + if (!client->dev.of_node) { + pdata = client->dev.platform_data; + if (!pdata) + dev_dbg(&client->dev, "Platform data not found\n"); + } else { + pdata = devm_kzalloc(&client->dev, + sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, "Failed to allocate pdata\n"); + return -ENOMEM; + } + } as3711 = devm_kzalloc(&client->dev, sizeof(struct as3711), GFP_KERNEL); if (!as3711) { @@ -193,7 +211,8 @@ static struct i2c_driver as3711_i2c_driver = { .driver = { .name = "as3711", .owner = THIS_MODULE, - }, + .of_match_table = of_match_ptr(as3711_of_match), + }, .probe = as3711_i2c_probe, .remove = as3711_i2c_remove, .id_table = as3711_i2c_id, diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c new file mode 100644 index 000000000000..10cd14e35eb0 --- /dev/null +++ b/drivers/mfd/cros_ec.c @@ -0,0 +1,196 @@ +/* + * ChromeOS EC multi-function device + * + * Copyright (C) 2012 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * The ChromeOS EC multi function device is used to mux all the requests + * to the EC device for its multiple features: keyboard controller, + * battery charging and regulator control, firmware update. + */ + +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/mfd/core.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> + +int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, + struct cros_ec_msg *msg) +{ + uint8_t *out; + int csum, i; + + BUG_ON(msg->out_len > EC_HOST_PARAM_SIZE); + out = ec_dev->dout; + out[0] = EC_CMD_VERSION0 + msg->version; + out[1] = msg->cmd; + out[2] = msg->out_len; + csum = out[0] + out[1] + out[2]; + for (i = 0; i < msg->out_len; i++) + csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->out_buf[i]; + out[EC_MSG_TX_HEADER_BYTES + msg->out_len] = (uint8_t)(csum & 0xff); + + return EC_MSG_TX_PROTO_BYTES + msg->out_len; +} +EXPORT_SYMBOL(cros_ec_prepare_tx); + +static int cros_ec_command_sendrecv(struct cros_ec_device *ec_dev, + uint16_t cmd, void *out_buf, int out_len, + void *in_buf, int in_len) +{ + struct cros_ec_msg msg; + + msg.version = cmd >> 8; + msg.cmd = cmd & 0xff; + msg.out_buf = out_buf; + msg.out_len = out_len; + msg.in_buf = in_buf; + msg.in_len = in_len; + + return ec_dev->command_xfer(ec_dev, &msg); +} + +static int cros_ec_command_recv(struct cros_ec_device *ec_dev, + uint16_t cmd, void *buf, int buf_len) +{ + return cros_ec_command_sendrecv(ec_dev, cmd, NULL, 0, buf, buf_len); +} + +static int cros_ec_command_send(struct cros_ec_device *ec_dev, + uint16_t cmd, void *buf, int buf_len) +{ + return cros_ec_command_sendrecv(ec_dev, cmd, buf, buf_len, NULL, 0); +} + +static irqreturn_t ec_irq_thread(int irq, void *data) +{ + struct cros_ec_device *ec_dev = data; + + if (device_may_wakeup(ec_dev->dev)) + pm_wakeup_event(ec_dev->dev, 0); + + blocking_notifier_call_chain(&ec_dev->event_notifier, 1, ec_dev); + + return IRQ_HANDLED; +} + +static struct mfd_cell cros_devs[] = { + { + .name = "cros-ec-keyb", + .id = 1, + .of_compatible = "google,cros-ec-keyb", + }, +}; + +int cros_ec_register(struct cros_ec_device *ec_dev) +{ + struct device *dev = ec_dev->dev; + int err = 0; + + BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier); + + ec_dev->command_send = cros_ec_command_send; + ec_dev->command_recv = cros_ec_command_recv; + ec_dev->command_sendrecv = cros_ec_command_sendrecv; + + if (ec_dev->din_size) { + ec_dev->din = kmalloc(ec_dev->din_size, GFP_KERNEL); + if (!ec_dev->din) { + err = -ENOMEM; + goto fail_din; + } + } + if (ec_dev->dout_size) { + ec_dev->dout = kmalloc(ec_dev->dout_size, GFP_KERNEL); + if (!ec_dev->dout) { + err = -ENOMEM; + goto fail_dout; + } + } + + if (!ec_dev->irq) { + dev_dbg(dev, "no valid IRQ: %d\n", ec_dev->irq); + goto fail_irq; + } + + err = request_threaded_irq(ec_dev->irq, NULL, ec_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "chromeos-ec", ec_dev); + if (err) { + dev_err(dev, "request irq %d: error %d\n", ec_dev->irq, err); + goto fail_irq; + } + + err = mfd_add_devices(dev, 0, cros_devs, + ARRAY_SIZE(cros_devs), + NULL, ec_dev->irq, NULL); + if (err) { + dev_err(dev, "failed to add mfd devices\n"); + goto fail_mfd; + } + + dev_info(dev, "Chrome EC (%s)\n", ec_dev->name); + + return 0; + +fail_mfd: + free_irq(ec_dev->irq, ec_dev); +fail_irq: + kfree(ec_dev->dout); +fail_dout: + kfree(ec_dev->din); +fail_din: + return err; +} +EXPORT_SYMBOL(cros_ec_register); + +int cros_ec_remove(struct cros_ec_device *ec_dev) +{ + mfd_remove_devices(ec_dev->dev); + free_irq(ec_dev->irq, ec_dev); + kfree(ec_dev->dout); + kfree(ec_dev->din); + + return 0; +} +EXPORT_SYMBOL(cros_ec_remove); + +#ifdef CONFIG_PM_SLEEP +int cros_ec_suspend(struct cros_ec_device *ec_dev) +{ + struct device *dev = ec_dev->dev; + + if (device_may_wakeup(dev)) + ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq); + + disable_irq(ec_dev->irq); + ec_dev->was_wake_device = ec_dev->wake_enabled; + + return 0; +} +EXPORT_SYMBOL(cros_ec_suspend); + +int cros_ec_resume(struct cros_ec_device *ec_dev) +{ + enable_irq(ec_dev->irq); + + if (ec_dev->wake_enabled) { + disable_irq_wake(ec_dev->irq); + ec_dev->wake_enabled = 0; + } + + return 0; +} +EXPORT_SYMBOL(cros_ec_resume); + +#endif diff --git a/drivers/mfd/cros_ec_i2c.c b/drivers/mfd/cros_ec_i2c.c new file mode 100644 index 000000000000..123044608b63 --- /dev/null +++ b/drivers/mfd/cros_ec_i2c.c @@ -0,0 +1,201 @@ +/* + * ChromeOS EC multi-function device (I2C) + * + * Copyright (C) 2012 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +static inline struct cros_ec_device *to_ec_dev(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + return i2c_get_clientdata(client); +} + +static int cros_ec_command_xfer(struct cros_ec_device *ec_dev, + struct cros_ec_msg *msg) +{ + struct i2c_client *client = ec_dev->priv; + int ret = -ENOMEM; + int i; + int packet_len; + u8 *out_buf = NULL; + u8 *in_buf = NULL; + u8 sum; + struct i2c_msg i2c_msg[2]; + + i2c_msg[0].addr = client->addr; + i2c_msg[0].flags = 0; + i2c_msg[1].addr = client->addr; + i2c_msg[1].flags = I2C_M_RD; + + /* + * allocate larger packet (one byte for checksum, one byte for + * length, and one for result code) + */ + packet_len = msg->in_len + 3; + in_buf = kzalloc(packet_len, GFP_KERNEL); + if (!in_buf) + goto done; + i2c_msg[1].len = packet_len; + i2c_msg[1].buf = (char *)in_buf; + + /* + * allocate larger packet (one byte for checksum, one for + * command code, one for length, and one for command version) + */ + packet_len = msg->out_len + 4; + out_buf = kzalloc(packet_len, GFP_KERNEL); + if (!out_buf) + goto done; + i2c_msg[0].len = packet_len; + i2c_msg[0].buf = (char *)out_buf; + + out_buf[0] = EC_CMD_VERSION0 + msg->version; + out_buf[1] = msg->cmd; + out_buf[2] = msg->out_len; + + /* copy message payload and compute checksum */ + sum = out_buf[0] + out_buf[1] + out_buf[2]; + for (i = 0; i < msg->out_len; i++) { + out_buf[3 + i] = msg->out_buf[i]; + sum += out_buf[3 + i]; + } + out_buf[3 + msg->out_len] = sum; + + /* send command to EC and read answer */ + ret = i2c_transfer(client->adapter, i2c_msg, 2); + if (ret < 0) { + dev_err(ec_dev->dev, "i2c transfer failed: %d\n", ret); + goto done; + } else if (ret != 2) { + dev_err(ec_dev->dev, "failed to get response: %d\n", ret); + ret = -EIO; + goto done; + } + + /* check response error code */ + if (i2c_msg[1].buf[0]) { + dev_warn(ec_dev->dev, "command 0x%02x returned an error %d\n", + msg->cmd, i2c_msg[1].buf[0]); + ret = -EINVAL; + goto done; + } + + /* copy response packet payload and compute checksum */ + sum = in_buf[0] + in_buf[1]; + for (i = 0; i < msg->in_len; i++) { + msg->in_buf[i] = in_buf[2 + i]; + sum += in_buf[2 + i]; + } + dev_dbg(ec_dev->dev, "packet: %*ph, sum = %02x\n", + i2c_msg[1].len, in_buf, sum); + if (sum != in_buf[2 + msg->in_len]) { + dev_err(ec_dev->dev, "bad packet checksum\n"); + ret = -EBADMSG; + goto done; + } + + ret = 0; + done: + kfree(in_buf); + kfree(out_buf); + return ret; +} + +static int cros_ec_probe_i2c(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + struct device *dev = &client->dev; + struct cros_ec_device *ec_dev = NULL; + int err; + + ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); + if (!ec_dev) + return -ENOMEM; + + i2c_set_clientdata(client, ec_dev); + ec_dev->name = "I2C"; + ec_dev->dev = dev; + ec_dev->priv = client; + ec_dev->irq = client->irq; + ec_dev->command_xfer = cros_ec_command_xfer; + ec_dev->ec_name = client->name; + ec_dev->phys_name = client->adapter->name; + ec_dev->parent = &client->dev; + + err = cros_ec_register(ec_dev); + if (err) { + dev_err(dev, "cannot register EC\n"); + return err; + } + + return 0; +} + +static int cros_ec_remove_i2c(struct i2c_client *client) +{ + struct cros_ec_device *ec_dev = i2c_get_clientdata(client); + + cros_ec_remove(ec_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cros_ec_i2c_suspend(struct device *dev) +{ + struct cros_ec_device *ec_dev = to_ec_dev(dev); + + return cros_ec_suspend(ec_dev); +} + +static int cros_ec_i2c_resume(struct device *dev) +{ + struct cros_ec_device *ec_dev = to_ec_dev(dev); + + return cros_ec_resume(ec_dev); +} +#endif + +static SIMPLE_DEV_PM_OPS(cros_ec_i2c_pm_ops, cros_ec_i2c_suspend, + cros_ec_i2c_resume); + +static const struct i2c_device_id cros_ec_i2c_id[] = { + { "cros-ec-i2c", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cros_ec_i2c_id); + +static struct i2c_driver cros_ec_driver = { + .driver = { + .name = "cros-ec-i2c", + .owner = THIS_MODULE, + .pm = &cros_ec_i2c_pm_ops, + }, + .probe = cros_ec_probe_i2c, + .remove = cros_ec_remove_i2c, + .id_table = cros_ec_i2c_id, +}; + +module_i2c_driver(cros_ec_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ChromeOS EC multi function device"); diff --git a/drivers/mfd/cros_ec_spi.c b/drivers/mfd/cros_ec_spi.c new file mode 100644 index 000000000000..19193cf1e7a1 --- /dev/null +++ b/drivers/mfd/cros_ec_spi.c @@ -0,0 +1,375 @@ +/* + * ChromeOS EC multi-function device (SPI) + * + * Copyright (C) 2012 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> + + +/* The header byte, which follows the preamble */ +#define EC_MSG_HEADER 0xec + +/* + * Number of EC preamble bytes we read at a time. Since it takes + * about 400-500us for the EC to respond there is not a lot of + * point in tuning this. If the EC could respond faster then + * we could increase this so that might expect the preamble and + * message to occur in a single transaction. However, the maximum + * SPI transfer size is 256 bytes, so at 5MHz we need a response + * time of perhaps <320us (200 bytes / 1600 bits). + */ +#define EC_MSG_PREAMBLE_COUNT 32 + +/* + * We must get a response from the EC in 5ms. This is a very long + * time, but the flash write command can take 2-3ms. The EC command + * processing is currently not very fast (about 500us). We could + * look at speeding this up and making the flash write command a + * 'slow' command, requiring a GET_STATUS wait loop, like flash + * erase. + */ +#define EC_MSG_DEADLINE_MS 5 + +/* + * Time between raising the SPI chip select (for the end of a + * transaction) and dropping it again (for the next transaction). + * If we go too fast, the EC will miss the transaction. It seems + * that 50us is enough with the 16MHz STM32 EC. + */ +#define EC_SPI_RECOVERY_TIME_NS (50 * 1000) + +/** + * struct cros_ec_spi - information about a SPI-connected EC + * + * @spi: SPI device we are connected to + * @last_transfer_ns: time that we last finished a transfer, or 0 if there + * if no record + */ +struct cros_ec_spi { + struct spi_device *spi; + s64 last_transfer_ns; +}; + +static void debug_packet(struct device *dev, const char *name, u8 *ptr, + int len) +{ +#ifdef DEBUG + int i; + + dev_dbg(dev, "%s: ", name); + for (i = 0; i < len; i++) + dev_cont(dev, " %02x", ptr[i]); +#endif +} + +/** + * cros_ec_spi_receive_response - Receive a response from the EC. + * + * This function has two phases: reading the preamble bytes (since if we read + * data from the EC before it is ready to send, we just get preamble) and + * reading the actual message. + * + * The received data is placed into ec_dev->din. + * + * @ec_dev: ChromeOS EC device + * @need_len: Number of message bytes we need to read + */ +static int cros_ec_spi_receive_response(struct cros_ec_device *ec_dev, + int need_len) +{ + struct cros_ec_spi *ec_spi = ec_dev->priv; + struct spi_transfer trans; + struct spi_message msg; + u8 *ptr, *end; + int ret; + unsigned long deadline; + int todo; + + /* Receive data until we see the header byte */ + deadline = jiffies + msecs_to_jiffies(EC_MSG_DEADLINE_MS); + do { + memset(&trans, '\0', sizeof(trans)); + trans.cs_change = 1; + trans.rx_buf = ptr = ec_dev->din; + trans.len = EC_MSG_PREAMBLE_COUNT; + + spi_message_init(&msg); + spi_message_add_tail(&trans, &msg); + ret = spi_sync(ec_spi->spi, &msg); + if (ret < 0) { + dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + return ret; + } + + for (end = ptr + EC_MSG_PREAMBLE_COUNT; ptr != end; ptr++) { + if (*ptr == EC_MSG_HEADER) { + dev_dbg(ec_dev->dev, "msg found at %ld\n", + ptr - ec_dev->din); + break; + } + } + + if (time_after(jiffies, deadline)) { + dev_warn(ec_dev->dev, "EC failed to respond in time\n"); + return -ETIMEDOUT; + } + } while (ptr == end); + + /* + * ptr now points to the header byte. Copy any valid data to the + * start of our buffer + */ + todo = end - ++ptr; + BUG_ON(todo < 0 || todo > ec_dev->din_size); + todo = min(todo, need_len); + memmove(ec_dev->din, ptr, todo); + ptr = ec_dev->din + todo; + dev_dbg(ec_dev->dev, "need %d, got %d bytes from preamble\n", + need_len, todo); + need_len -= todo; + + /* Receive data until we have it all */ + while (need_len > 0) { + /* + * We can't support transfers larger than the SPI FIFO size + * unless we have DMA. We don't have DMA on the ISP SPI ports + * for Exynos. We need a way of asking SPI driver for + * maximum-supported transfer size. + */ + todo = min(need_len, 256); + dev_dbg(ec_dev->dev, "loop, todo=%d, need_len=%d, ptr=%ld\n", + todo, need_len, ptr - ec_dev->din); + + memset(&trans, '\0', sizeof(trans)); + trans.cs_change = 1; + trans.rx_buf = ptr; + trans.len = todo; + spi_message_init(&msg); + spi_message_add_tail(&trans, &msg); + + /* send command to EC and read answer */ + BUG_ON((u8 *)trans.rx_buf - ec_dev->din + todo > + ec_dev->din_size); + ret = spi_sync(ec_spi->spi, &msg); + if (ret < 0) { + dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + return ret; + } + + debug_packet(ec_dev->dev, "interim", ptr, todo); + ptr += todo; + need_len -= todo; + } + + dev_dbg(ec_dev->dev, "loop done, ptr=%ld\n", ptr - ec_dev->din); + + return 0; +} + +/** + * cros_ec_command_spi_xfer - Transfer a message over SPI and receive the reply + * + * @ec_dev: ChromeOS EC device + * @ec_msg: Message to transfer + */ +static int cros_ec_command_spi_xfer(struct cros_ec_device *ec_dev, + struct cros_ec_msg *ec_msg) +{ + struct cros_ec_spi *ec_spi = ec_dev->priv; + struct spi_transfer trans; + struct spi_message msg; + int i, len; + u8 *ptr; + int sum; + int ret = 0, final_ret; + struct timespec ts; + + len = cros_ec_prepare_tx(ec_dev, ec_msg); + dev_dbg(ec_dev->dev, "prepared, len=%d\n", len); + + /* If it's too soon to do another transaction, wait */ + if (ec_spi->last_transfer_ns) { + struct timespec ts; + unsigned long delay; /* The delay completed so far */ + + ktime_get_ts(&ts); + delay = timespec_to_ns(&ts) - ec_spi->last_transfer_ns; + if (delay < EC_SPI_RECOVERY_TIME_NS) + ndelay(delay); + } + + /* Transmit phase - send our message */ + debug_packet(ec_dev->dev, "out", ec_dev->dout, len); + memset(&trans, '\0', sizeof(trans)); + trans.tx_buf = ec_dev->dout; + trans.len = len; + trans.cs_change = 1; + spi_message_init(&msg); + spi_message_add_tail(&trans, &msg); + ret = spi_sync(ec_spi->spi, &msg); + + /* Get the response */ + if (!ret) { + ret = cros_ec_spi_receive_response(ec_dev, + ec_msg->in_len + EC_MSG_TX_PROTO_BYTES); + } else { + dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + } + + /* turn off CS */ + spi_message_init(&msg); + final_ret = spi_sync(ec_spi->spi, &msg); + ktime_get_ts(&ts); + ec_spi->last_transfer_ns = timespec_to_ns(&ts); + if (!ret) + ret = final_ret; + if (ret < 0) { + dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + return ret; + } + + /* check response error code */ + ptr = ec_dev->din; + if (ptr[0]) { + dev_warn(ec_dev->dev, "command 0x%02x returned an error %d\n", + ec_msg->cmd, ptr[0]); + debug_packet(ec_dev->dev, "in_err", ptr, len); + return -EINVAL; + } + len = ptr[1]; + sum = ptr[0] + ptr[1]; + if (len > ec_msg->in_len) { + dev_err(ec_dev->dev, "packet too long (%d bytes, expected %d)", + len, ec_msg->in_len); + return -ENOSPC; + } + + /* copy response packet payload and compute checksum */ + for (i = 0; i < len; i++) { + sum += ptr[i + 2]; + if (ec_msg->in_len) + ec_msg->in_buf[i] = ptr[i + 2]; + } + sum &= 0xff; + + debug_packet(ec_dev->dev, "in", ptr, len + 3); + + if (sum != ptr[len + 2]) { + dev_err(ec_dev->dev, + "bad packet checksum, expected %02x, got %02x\n", + sum, ptr[len + 2]); + return -EBADMSG; + } + + return 0; +} + +static int cros_ec_probe_spi(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct cros_ec_device *ec_dev; + struct cros_ec_spi *ec_spi; + int err; + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + err = spi_setup(spi); + if (err < 0) + return err; + + ec_spi = devm_kzalloc(dev, sizeof(*ec_spi), GFP_KERNEL); + if (ec_spi == NULL) + return -ENOMEM; + ec_spi->spi = spi; + ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); + if (!ec_dev) + return -ENOMEM; + + spi_set_drvdata(spi, ec_dev); + ec_dev->name = "SPI"; + ec_dev->dev = dev; + ec_dev->priv = ec_spi; + ec_dev->irq = spi->irq; + ec_dev->command_xfer = cros_ec_command_spi_xfer; + ec_dev->ec_name = ec_spi->spi->modalias; + ec_dev->phys_name = dev_name(&ec_spi->spi->dev); + ec_dev->parent = &ec_spi->spi->dev; + ec_dev->din_size = EC_MSG_BYTES + EC_MSG_PREAMBLE_COUNT; + ec_dev->dout_size = EC_MSG_BYTES; + + err = cros_ec_register(ec_dev); + if (err) { + dev_err(dev, "cannot register EC\n"); + return err; + } + + return 0; +} + +static int cros_ec_remove_spi(struct spi_device *spi) +{ + struct cros_ec_device *ec_dev; + + ec_dev = spi_get_drvdata(spi); + cros_ec_remove(ec_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cros_ec_spi_suspend(struct device *dev) +{ + struct cros_ec_device *ec_dev = dev_get_drvdata(dev); + + return cros_ec_suspend(ec_dev); +} + +static int cros_ec_spi_resume(struct device *dev) +{ + struct cros_ec_device *ec_dev = dev_get_drvdata(dev); + + return cros_ec_resume(ec_dev); +} +#endif + +static SIMPLE_DEV_PM_OPS(cros_ec_spi_pm_ops, cros_ec_spi_suspend, + cros_ec_spi_resume); + +static const struct spi_device_id cros_ec_spi_id[] = { + { "cros-ec-spi", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, cros_ec_spi_id); + +static struct spi_driver cros_ec_driver_spi = { + .driver = { + .name = "cros-ec-spi", + .owner = THIS_MODULE, + .pm = &cros_ec_spi_pm_ops, + }, + .probe = cros_ec_probe_spi, + .remove = cros_ec_remove_spi, + .id_table = cros_ec_spi_id, +}; + +module_spi_driver(cros_ec_driver_spi); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ChromeOS EC multi function device (SPI)"); diff --git a/drivers/mfd/da903x.c b/drivers/mfd/da903x.c index 05176cd2862b..f1a316e0d6a6 100644 --- a/drivers/mfd/da903x.c +++ b/drivers/mfd/da903x.c @@ -499,7 +499,8 @@ static int da903x_probe(struct i2c_client *client, unsigned int tmp; int ret; - chip = kzalloc(sizeof(struct da903x_chip), GFP_KERNEL); + chip = devm_kzalloc(&client->dev, sizeof(struct da903x_chip), + GFP_KERNEL); if (chip == NULL) return -ENOMEM; @@ -515,33 +516,27 @@ static int da903x_probe(struct i2c_client *client, ret = chip->ops->init_chip(chip); if (ret) - goto out_free_chip; + return ret; /* 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, + ret = devm_request_irq(&client->dev, client->irq, da903x_irq_handler, IRQF_TRIGGER_FALLING, "da903x", chip); if (ret) { dev_err(&client->dev, "failed to request irq %d\n", client->irq); - goto out_free_chip; + return ret; } ret = da903x_add_subdevs(chip, pdata); if (ret) - goto out_free_irq; + return ret; return 0; - -out_free_irq: - free_irq(client->irq, chip); -out_free_chip: - kfree(chip); - return ret; } static int da903x_remove(struct i2c_client *client) @@ -549,8 +544,6 @@ static int da903x_remove(struct i2c_client *client) struct da903x_chip *chip = i2c_get_clientdata(client); da903x_remove_subdevs(chip); - free_irq(client->irq, chip); - kfree(chip); return 0; } diff --git a/drivers/mfd/da9052-spi.c b/drivers/mfd/da9052-spi.c index 61d63b93576c..0680bcbc53de 100644 --- a/drivers/mfd/da9052-spi.c +++ b/drivers/mfd/da9052-spi.c @@ -38,7 +38,7 @@ static int da9052_spi_probe(struct spi_device *spi) da9052->dev = &spi->dev; da9052->chip_irq = spi->irq; - dev_set_drvdata(&spi->dev, da9052); + spi_set_drvdata(spi, da9052); da9052_regmap_config.read_flag_mask = 1; da9052_regmap_config.write_flag_mask = 0; @@ -60,7 +60,7 @@ static int da9052_spi_probe(struct spi_device *spi) static int da9052_spi_remove(struct spi_device *spi) { - struct da9052 *da9052 = dev_get_drvdata(&spi->dev); + struct da9052 *da9052 = spi_get_drvdata(spi); da9052_device_exit(da9052); return 0; diff --git a/drivers/mfd/da9055-core.c b/drivers/mfd/da9055-core.c index f56a1a9f7777..49cb23d37469 100644 --- a/drivers/mfd/da9055-core.c +++ b/drivers/mfd/da9055-core.c @@ -391,7 +391,7 @@ int da9055_device_init(struct da9055 *da9055) da9055->irq_base = pdata->irq_base; ret = regmap_add_irq_chip(da9055->regmap, da9055->chip_irq, - IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, da9055->irq_base, &da9055_regmap_irq_chip, &da9055->irq_data); if (ret < 0) diff --git a/drivers/mfd/davinci_voicecodec.c b/drivers/mfd/davinci_voicecodec.c index c0bcc872af4e..c60ab0c3c4db 100644 --- a/drivers/mfd/davinci_voicecodec.c +++ b/drivers/mfd/davinci_voicecodec.c @@ -177,17 +177,7 @@ static struct platform_driver davinci_vc_driver = { .remove = davinci_vc_remove, }; -static int __init davinci_vc_init(void) -{ - return platform_driver_probe(&davinci_vc_driver, davinci_vc_probe); -} -module_init(davinci_vc_init); - -static void __exit davinci_vc_exit(void) -{ - platform_driver_unregister(&davinci_vc_driver); -} -module_exit(davinci_vc_exit); +module_platform_driver_probe(davinci_vc_driver, davinci_vc_probe); MODULE_AUTHOR("Miguel Aguilar"); MODULE_DESCRIPTION("Texas Instruments DaVinci Voice Codec Core Interface"); diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index 21f261bf9e95..319b8abe742b 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -24,9 +24,9 @@ #include <linux/jiffies.h> #include <linux/bitops.h> #include <linux/fs.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/uaccess.h> -#include <linux/irqchip/arm-gic.h> #include <linux/mfd/core.h> #include <linux/mfd/dbx500-prcmu.h> #include <linux/mfd/abx500/ab8500.h> @@ -34,9 +34,7 @@ #include <linux/regulator/machine.h> #include <linux/cpufreq.h> #include <linux/platform_data/ux500_wdt.h> -#include <mach/hardware.h> -#include <mach/irqs.h> -#include <mach/db8500-regs.h> +#include <linux/platform_data/db8500_thermal.h> #include "dbx500-prcmu-regs.h" /* Index of different voltages to be used when accessing AVSData */ @@ -276,8 +274,34 @@ static struct irq_domain *db8500_irq_domain; * the bits in the bit field are not. (The bits also have a tendency to move * around, to further complicate matters.) */ -#define IRQ_INDEX(_name) ((IRQ_PRCMU_##_name) - IRQ_PRCMU_BASE) +#define IRQ_INDEX(_name) ((IRQ_PRCMU_##_name)) #define IRQ_ENTRY(_name)[IRQ_INDEX(_name)] = (WAKEUP_BIT_##_name) + +#define IRQ_PRCMU_RTC 0 +#define IRQ_PRCMU_RTT0 1 +#define IRQ_PRCMU_RTT1 2 +#define IRQ_PRCMU_HSI0 3 +#define IRQ_PRCMU_HSI1 4 +#define IRQ_PRCMU_CA_WAKE 5 +#define IRQ_PRCMU_USB 6 +#define IRQ_PRCMU_ABB 7 +#define IRQ_PRCMU_ABB_FIFO 8 +#define IRQ_PRCMU_ARM 9 +#define IRQ_PRCMU_MODEM_SW_RESET_REQ 10 +#define IRQ_PRCMU_GPIO0 11 +#define IRQ_PRCMU_GPIO1 12 +#define IRQ_PRCMU_GPIO2 13 +#define IRQ_PRCMU_GPIO3 14 +#define IRQ_PRCMU_GPIO4 15 +#define IRQ_PRCMU_GPIO5 16 +#define IRQ_PRCMU_GPIO6 17 +#define IRQ_PRCMU_GPIO7 18 +#define IRQ_PRCMU_GPIO8 19 +#define IRQ_PRCMU_CA_SLEEP 20 +#define IRQ_PRCMU_HOTMON_LOW 21 +#define IRQ_PRCMU_HOTMON_HIGH 22 +#define NUM_PRCMU_WAKEUPS 23 + static u32 prcmu_irq_bit[NUM_PRCMU_WAKEUPS] = { IRQ_ENTRY(RTC), IRQ_ENTRY(RTT0), @@ -422,9 +446,10 @@ static DEFINE_SPINLOCK(clkout_lock); /* Global var to runtime determine TCDM base for v2 or v1 */ static __iomem void *tcdm_base; +static __iomem void *prcmu_base; struct clk_mgt { - void __iomem *reg; + u32 offset; u32 pllsw; int branch; bool clk38div; @@ -599,9 +624,9 @@ int db8500_prcmu_set_display_clocks(void) while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) cpu_relax(); - writel(PRCMU_DSI_CLOCK_SETTING, PRCM_HDMICLK_MGT); - writel(PRCMU_DSI_LP_CLOCK_SETTING, PRCM_TVCLK_MGT); - writel(PRCMU_DPI_CLOCK_SETTING, PRCM_LCDCLK_MGT); + writel(PRCMU_DSI_CLOCK_SETTING, prcmu_base + PRCM_HDMICLK_MGT); + writel(PRCMU_DSI_LP_CLOCK_SETTING, prcmu_base + PRCM_TVCLK_MGT); + writel(PRCMU_DPI_CLOCK_SETTING, prcmu_base + PRCM_LCDCLK_MGT); /* Release the HW semaphore. */ writel(0, PRCM_SEM); @@ -613,7 +638,7 @@ int db8500_prcmu_set_display_clocks(void) u32 db8500_prcmu_read(unsigned int reg) { - return readl(_PRCMU_BASE + reg); + return readl(prcmu_base + reg); } void db8500_prcmu_write(unsigned int reg, u32 value) @@ -621,7 +646,7 @@ void db8500_prcmu_write(unsigned int reg, u32 value) unsigned long flags; spin_lock_irqsave(&prcmu_lock, flags); - writel(value, (_PRCMU_BASE + reg)); + writel(value, (prcmu_base + reg)); spin_unlock_irqrestore(&prcmu_lock, flags); } @@ -631,9 +656,9 @@ void db8500_prcmu_write_masked(unsigned int reg, u32 mask, u32 value) unsigned long flags; spin_lock_irqsave(&prcmu_lock, flags); - val = readl(_PRCMU_BASE + reg); + val = readl(prcmu_base + reg); val = ((val & ~mask) | (value & mask)); - writel(val, (_PRCMU_BASE + reg)); + writel(val, (prcmu_base + reg)); spin_unlock_irqrestore(&prcmu_lock, flags); } @@ -793,119 +818,6 @@ u8 db8500_prcmu_get_power_state_result(void) return readb(tcdm_base + PRCM_ACK_MB0_AP_PWRSTTR_STATUS); } -/* This function decouple the gic from the prcmu */ -int db8500_prcmu_gic_decouple(void) -{ - u32 val = readl(PRCM_A9_MASK_REQ); - - /* Set bit 0 register value to 1 */ - writel(val | PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ, - PRCM_A9_MASK_REQ); - - /* Make sure the register is updated */ - readl(PRCM_A9_MASK_REQ); - - /* Wait a few cycles for the gic mask completion */ - udelay(1); - - return 0; -} - -/* This function recouple the gic with the prcmu */ -int db8500_prcmu_gic_recouple(void) -{ - u32 val = readl(PRCM_A9_MASK_REQ); - - /* Set bit 0 register value to 0 */ - writel(val & ~PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ, PRCM_A9_MASK_REQ); - - return 0; -} - -#define PRCMU_GIC_NUMBER_REGS 5 - -/* - * This function checks if there are pending irq on the gic. It only - * makes sense if the gic has been decoupled before with the - * db8500_prcmu_gic_decouple function. Disabling an interrupt only - * disables the forwarding of the interrupt to any CPU interface. It - * does not prevent the interrupt from changing state, for example - * becoming pending, or active and pending if it is already - * active. Hence, we have to check the interrupt is pending *and* is - * active. - */ -bool db8500_prcmu_gic_pending_irq(void) -{ - u32 pr; /* Pending register */ - u32 er; /* Enable register */ - void __iomem *dist_base = __io_address(U8500_GIC_DIST_BASE); - int i; - - /* 5 registers. STI & PPI not skipped */ - for (i = 0; i < PRCMU_GIC_NUMBER_REGS; i++) { - - pr = readl_relaxed(dist_base + GIC_DIST_PENDING_SET + i * 4); - er = readl_relaxed(dist_base + GIC_DIST_ENABLE_SET + i * 4); - - if (pr & er) - return true; /* There is a pending interrupt */ - } - - return false; -} - -/* - * This function checks if there are pending interrupt on the - * prcmu which has been delegated to monitor the irqs with the - * db8500_prcmu_copy_gic_settings function. - */ -bool db8500_prcmu_pending_irq(void) -{ - u32 it, im; - int i; - - for (i = 0; i < PRCMU_GIC_NUMBER_REGS - 1; i++) { - it = readl(PRCM_ARMITVAL31TO0 + i * 4); - im = readl(PRCM_ARMITMSK31TO0 + i * 4); - if (it & im) - return true; /* There is a pending interrupt */ - } - - return false; -} - -/* - * This function checks if the specified cpu is in in WFI. It's usage - * makes sense only if the gic is decoupled with the db8500_prcmu_gic_decouple - * function. Of course passing smp_processor_id() to this function will - * always return false... - */ -bool db8500_prcmu_is_cpu_in_wfi(int cpu) -{ - return readl(PRCM_ARM_WFI_STANDBY) & cpu ? PRCM_ARM_WFI_STANDBY_WFI1 : - PRCM_ARM_WFI_STANDBY_WFI0; -} - -/* - * This function copies the gic SPI settings to the prcmu in order to - * monitor them and abort/finish the retention/off sequence or state. - */ -int db8500_prcmu_copy_gic_settings(void) -{ - u32 er; /* Enable register */ - void __iomem *dist_base = __io_address(U8500_GIC_DIST_BASE); - int i; - - /* We skip the STI and PPI */ - for (i = 0; i < PRCMU_GIC_NUMBER_REGS - 1; i++) { - er = readl_relaxed(dist_base + - GIC_DIST_ENABLE_SET + (i + 1) * 4); - writel(er, PRCM_ARMITMSK31TO0 + i * 4); - } - - return 0; -} - /* This function should only be called while mb0_transfer.lock is held. */ static void config_wakeups(void) { @@ -1059,7 +971,7 @@ int db8500_prcmu_set_ddr_opp(u8 opp) /* Divide the frequency of certain clocks by 2 for APE_50_PARTLY_25_OPP. */ static void request_even_slower_clocks(bool enable) { - void __iomem *clock_reg[] = { + u32 clock_reg[] = { PRCM_ACLK_MGT, PRCM_DMACLK_MGT }; @@ -1076,7 +988,7 @@ static void request_even_slower_clocks(bool enable) u32 val; u32 div; - val = readl(clock_reg[i]); + val = readl(prcmu_base + clock_reg[i]); div = (val & PRCM_CLK_MGT_CLKPLLDIV_MASK); if (enable) { if ((div <= 1) || (div > 15)) { @@ -1092,7 +1004,7 @@ static void request_even_slower_clocks(bool enable) } val = ((val & ~PRCM_CLK_MGT_CLKPLLDIV_MASK) | (div & PRCM_CLK_MGT_CLKPLLDIV_MASK)); - writel(val, clock_reg[i]); + writel(val, prcmu_base + clock_reg[i]); } unlock_and_return: @@ -1446,14 +1358,14 @@ static int request_clock(u8 clock, bool enable) while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) cpu_relax(); - val = readl(clk_mgt[clock].reg); + val = readl(prcmu_base + clk_mgt[clock].offset); if (enable) { val |= (PRCM_CLK_MGT_CLKEN | clk_mgt[clock].pllsw); } else { clk_mgt[clock].pllsw = (val & PRCM_CLK_MGT_CLKPLLSW_MASK); val &= ~(PRCM_CLK_MGT_CLKEN | PRCM_CLK_MGT_CLKPLLSW_MASK); } - writel(val, clk_mgt[clock].reg); + writel(val, prcmu_base + clk_mgt[clock].offset); /* Release the HW semaphore. */ writel(0, PRCM_SEM); @@ -1629,7 +1541,7 @@ static unsigned long clock_rate(u8 clock) u32 pllsw; unsigned long rate = ROOT_CLOCK_RATE; - val = readl(clk_mgt[clock].reg); + val = readl(prcmu_base + clk_mgt[clock].offset); if (val & PRCM_CLK_MGT_CLK38) { if (clk_mgt[clock].clk38div && (val & PRCM_CLK_MGT_CLK38DIV)) @@ -1785,7 +1697,7 @@ static long round_clock_rate(u8 clock, unsigned long rate) unsigned long src_rate; long rounded_rate; - val = readl(clk_mgt[clock].reg); + val = readl(prcmu_base + clk_mgt[clock].offset); src_rate = clock_source_rate((val | clk_mgt[clock].pllsw), clk_mgt[clock].branch); div = clock_divider(src_rate, rate); @@ -1933,7 +1845,7 @@ static void set_clock_rate(u8 clock, unsigned long rate) while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) cpu_relax(); - val = readl(clk_mgt[clock].reg); + val = readl(prcmu_base + clk_mgt[clock].offset); src_rate = clock_source_rate((val | clk_mgt[clock].pllsw), clk_mgt[clock].branch); div = clock_divider(src_rate, rate); @@ -1961,7 +1873,7 @@ static void set_clock_rate(u8 clock, unsigned long rate) val &= ~PRCM_CLK_MGT_CLKPLLDIV_MASK; val |= min(div, (u32)31); } - writel(val, clk_mgt[clock].reg); + writel(val, prcmu_base + clk_mgt[clock].offset); /* Release the HW semaphore. */ writel(0, PRCM_SEM); @@ -2764,14 +2676,13 @@ static struct irq_domain_ops db8500_irq_ops = { .xlate = irq_domain_xlate_twocell, }; -static int db8500_irq_init(struct device_node *np) +static int db8500_irq_init(struct device_node *np, int irq_base) { - int irq_base = 0; int i; /* In the device tree case, just take some IRQs */ - if (!np) - irq_base = IRQ_PRCMU_BASE; + if (np) + irq_base = 0; db8500_irq_domain = irq_domain_add_simple( np, NUM_PRCMU_WAKEUPS, irq_base, @@ -2794,6 +2705,7 @@ static void dbx500_fw_version_init(struct platform_device *pdev, { struct resource *res; void __iomem *tcpm_base; + u32 version; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "prcmu-tcpm"); @@ -2803,30 +2715,42 @@ static void dbx500_fw_version_init(struct platform_device *pdev, return; } tcpm_base = ioremap(res->start, resource_size(res)); - if (tcpm_base != NULL) { - u32 version; - - version = readl(tcpm_base + version_offset); - fw_info.version.project = (version & 0xFF); - fw_info.version.api_version = (version >> 8) & 0xFF; - fw_info.version.func_version = (version >> 16) & 0xFF; - fw_info.version.errata = (version >> 24) & 0xFF; - strncpy(fw_info.version.project_name, - fw_project_name(fw_info.version.project), - PRCMU_FW_PROJECT_NAME_LEN); - fw_info.valid = true; - pr_info("PRCMU firmware: %s(%d), version %d.%d.%d\n", - fw_info.version.project_name, - fw_info.version.project, - fw_info.version.api_version, - fw_info.version.func_version, - fw_info.version.errata); - iounmap(tcpm_base); + if (!tcpm_base) { + dev_err(&pdev->dev, "no prcmu tcpm mem region provided\n"); + return; } -} -void __init db8500_prcmu_early_init(void) + version = readl(tcpm_base + version_offset); + fw_info.version.project = (version & 0xFF); + fw_info.version.api_version = (version >> 8) & 0xFF; + fw_info.version.func_version = (version >> 16) & 0xFF; + fw_info.version.errata = (version >> 24) & 0xFF; + strncpy(fw_info.version.project_name, + fw_project_name(fw_info.version.project), + PRCMU_FW_PROJECT_NAME_LEN); + fw_info.valid = true; + pr_info("PRCMU firmware: %s(%d), version %d.%d.%d\n", + fw_info.version.project_name, + fw_info.version.project, + fw_info.version.api_version, + fw_info.version.func_version, + fw_info.version.errata); + iounmap(tcpm_base); +} + +void __init db8500_prcmu_early_init(u32 phy_base, u32 size) { + /* + * This is a temporary remap to bring up the clocks. It is + * subsequently replaces with a real remap. After the merge of + * the mailbox subsystem all of this early code goes away, and the + * clock driver can probe independently. An early initcall will + * still be needed, but it can be diverted into drivers/clk/ux500. + */ + prcmu_base = ioremap(phy_base, size); + if (!prcmu_base) + pr_err("%s: ioremap() of prcmu registers failed!\n", __func__); + spin_lock_init(&mb0_transfer.lock); spin_lock_init(&mb0_transfer.dbb_irqs_lock); mutex_init(&mb0_transfer.ac_wake_lock); @@ -3092,18 +3016,66 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { }, }; -static struct resource ab8500_resources[] = { - [0] = { - .start = IRQ_DB8500_AB8500, - .end = IRQ_DB8500_AB8500, - .flags = IORESOURCE_IRQ - } -}; - static struct ux500_wdt_data db8500_wdt_pdata = { .timeout = 600, /* 10 minutes */ .has_28_bits_resolution = true, }; +/* + * Thermal Sensor + */ + +static struct resource db8500_thsens_resources[] = { + { + .name = "IRQ_HOTMON_LOW", + .start = IRQ_PRCMU_HOTMON_LOW, + .end = IRQ_PRCMU_HOTMON_LOW, + .flags = IORESOURCE_IRQ, + }, + { + .name = "IRQ_HOTMON_HIGH", + .start = IRQ_PRCMU_HOTMON_HIGH, + .end = IRQ_PRCMU_HOTMON_HIGH, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct db8500_thsens_platform_data db8500_thsens_data = { + .trip_points[0] = { + .temp = 70000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[1] = { + .temp = 75000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[2] = { + .temp = 80000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[3] = { + .temp = 85000, + .type = THERMAL_TRIP_CRITICAL, + }, + .num_trips = 4, +}; + +static struct mfd_cell common_prcmu_devs[] = { + { + .name = "ux500_wdt", + .platform_data = &db8500_wdt_pdata, + .pdata_size = sizeof(db8500_wdt_pdata), + .id = -1, + }, +}; static struct mfd_cell db8500_prcmu_devs[] = { { @@ -3119,17 +3091,10 @@ static struct mfd_cell db8500_prcmu_devs[] = { .pdata_size = sizeof(db8500_cpufreq_table), }, { - .name = "ux500_wdt", - .platform_data = &db8500_wdt_pdata, - .pdata_size = sizeof(db8500_wdt_pdata), - .id = -1, - }, - { - .name = "ab8500-core", - .of_compatible = "stericsson,ab8500", - .num_resources = ARRAY_SIZE(ab8500_resources), - .resources = ab8500_resources, - .id = AB8500_VERSION_AB8500, + .name = "db8500-thermal", + .num_resources = ARRAY_SIZE(db8500_thsens_resources), + .resources = db8500_thsens_resources, + .platform_data = &db8500_thsens_data, }, }; @@ -3141,6 +3106,24 @@ static void db8500_prcmu_update_cpufreq(void) } } +static int db8500_prcmu_register_ab8500(struct device *parent, + struct ab8500_platform_data *pdata, + int irq) +{ + struct resource ab8500_resource = DEFINE_RES_IRQ(irq); + struct mfd_cell ab8500_cell = { + .name = "ab8500-core", + .of_compatible = "stericsson,ab8500", + .id = AB8500_VERSION_AB8500, + .platform_data = pdata, + .pdata_size = sizeof(struct ab8500_platform_data), + .resources = &ab8500_resource, + .num_resources = 1, + }; + + return mfd_add_devices(parent, 0, &ab8500_cell, 1, NULL, 0, NULL); +} + /** * prcmu_fw_init - arch init call for the Linux PRCMU fw init logic * @@ -3149,11 +3132,21 @@ static int db8500_prcmu_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct prcmu_pdata *pdata = dev_get_platdata(&pdev->dev); - int irq = 0, err = 0, i; + int irq = 0, err = 0; struct resource *res; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "prcmu"); + if (!res) { + dev_err(&pdev->dev, "no prcmu memory region provided\n"); + return -ENOENT; + } + prcmu_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!prcmu_base) { + dev_err(&pdev->dev, + "failed to ioremap prcmu register memory\n"); + return -ENOENT; + } init_prcm_registers(); - dbx500_fw_version_init(pdev, pdata->version_offset); res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "prcmu-tcdm"); if (!res) { @@ -3180,26 +3173,39 @@ static int db8500_prcmu_probe(struct platform_device *pdev) goto no_irq_return; } - db8500_irq_init(np); - - for (i = 0; i < ARRAY_SIZE(db8500_prcmu_devs); i++) { - if (!strcmp(db8500_prcmu_devs[i].name, "ab8500-core")) { - db8500_prcmu_devs[i].platform_data = pdata->ab_platdata; - db8500_prcmu_devs[i].pdata_size = sizeof(struct ab8500_platform_data); - } - } + db8500_irq_init(np, pdata->irq_base); prcmu_config_esram0_deep_sleep(ESRAM0_DEEP_SLEEP_STATE_RET); db8500_prcmu_update_cpufreq(); - err = mfd_add_devices(&pdev->dev, 0, db8500_prcmu_devs, - ARRAY_SIZE(db8500_prcmu_devs), NULL, 0, NULL); + err = mfd_add_devices(&pdev->dev, 0, common_prcmu_devs, + ARRAY_SIZE(common_prcmu_devs), NULL, 0, db8500_irq_domain); if (err) { pr_err("prcmu: Failed to add subdevices\n"); return err; } + /* TODO: Remove restriction when clk definitions are available. */ + if (!of_machine_is_compatible("st-ericsson,u8540")) { + err = mfd_add_devices(&pdev->dev, 0, db8500_prcmu_devs, + ARRAY_SIZE(db8500_prcmu_devs), NULL, 0, + db8500_irq_domain); + if (err) { + mfd_remove_devices(&pdev->dev); + pr_err("prcmu: Failed to add subdevices\n"); + goto no_irq_return; + } + } + + err = db8500_prcmu_register_ab8500(&pdev->dev, pdata->ab_platdata, + pdata->ab_irq); + if (err) { + mfd_remove_devices(&pdev->dev); + pr_err("prcmu: Failed to add ab8500 subdevice\n"); + goto no_irq_return; + } + pr_info("DB8500 PRCMU initialized\n"); no_irq_return: diff --git a/drivers/mfd/dbx500-prcmu-regs.h b/drivers/mfd/dbx500-prcmu-regs.h index 79c76ebdba52..d14836ed2114 100644 --- a/drivers/mfd/dbx500-prcmu-regs.h +++ b/drivers/mfd/dbx500-prcmu-regs.h @@ -13,136 +13,110 @@ #ifndef __DB8500_PRCMU_REGS_H #define __DB8500_PRCMU_REGS_H -#include <mach/hardware.h> - #define BITS(_start, _end) ((BIT(_end) - BIT(_start)) + BIT(_end)) -#define PRCM_CLK_MGT(_offset) (void __iomem *)(IO_ADDRESS(U8500_PRCMU_BASE) \ - + _offset) -#define PRCM_ACLK_MGT PRCM_CLK_MGT(0x004) -#define PRCM_SVACLK_MGT PRCM_CLK_MGT(0x008) -#define PRCM_SIACLK_MGT PRCM_CLK_MGT(0x00C) -#define PRCM_SGACLK_MGT PRCM_CLK_MGT(0x014) -#define PRCM_UARTCLK_MGT PRCM_CLK_MGT(0x018) -#define PRCM_MSP02CLK_MGT PRCM_CLK_MGT(0x01C) -#define PRCM_I2CCLK_MGT PRCM_CLK_MGT(0x020) -#define PRCM_SDMMCCLK_MGT PRCM_CLK_MGT(0x024) -#define PRCM_SLIMCLK_MGT PRCM_CLK_MGT(0x028) -#define PRCM_PER1CLK_MGT PRCM_CLK_MGT(0x02C) -#define PRCM_PER2CLK_MGT PRCM_CLK_MGT(0x030) -#define PRCM_PER3CLK_MGT PRCM_CLK_MGT(0x034) -#define PRCM_PER5CLK_MGT PRCM_CLK_MGT(0x038) -#define PRCM_PER6CLK_MGT PRCM_CLK_MGT(0x03C) -#define PRCM_PER7CLK_MGT PRCM_CLK_MGT(0x040) -#define PRCM_LCDCLK_MGT PRCM_CLK_MGT(0x044) -#define PRCM_BMLCLK_MGT PRCM_CLK_MGT(0x04C) -#define PRCM_HSITXCLK_MGT PRCM_CLK_MGT(0x050) -#define PRCM_HSIRXCLK_MGT PRCM_CLK_MGT(0x054) -#define PRCM_HDMICLK_MGT PRCM_CLK_MGT(0x058) -#define PRCM_APEATCLK_MGT PRCM_CLK_MGT(0x05C) -#define PRCM_APETRACECLK_MGT PRCM_CLK_MGT(0x060) -#define PRCM_MCDECLK_MGT PRCM_CLK_MGT(0x064) -#define PRCM_IPI2CCLK_MGT PRCM_CLK_MGT(0x068) -#define PRCM_DSIALTCLK_MGT PRCM_CLK_MGT(0x06C) -#define PRCM_DMACLK_MGT PRCM_CLK_MGT(0x074) -#define PRCM_B2R2CLK_MGT PRCM_CLK_MGT(0x078) -#define PRCM_TVCLK_MGT PRCM_CLK_MGT(0x07C) -#define PRCM_UNIPROCLK_MGT PRCM_CLK_MGT(0x278) -#define PRCM_SSPCLK_MGT PRCM_CLK_MGT(0x280) -#define PRCM_RNGCLK_MGT PRCM_CLK_MGT(0x284) -#define PRCM_UICCCLK_MGT PRCM_CLK_MGT(0x27C) -#define PRCM_MSP1CLK_MGT PRCM_CLK_MGT(0x288) - -#define PRCM_ARM_PLLDIVPS (_PRCMU_BASE + 0x118) +#define PRCM_ACLK_MGT (0x004) +#define PRCM_SVACLK_MGT (0x008) +#define PRCM_SIACLK_MGT (0x00C) +#define PRCM_SGACLK_MGT (0x014) +#define PRCM_UARTCLK_MGT (0x018) +#define PRCM_MSP02CLK_MGT (0x01C) +#define PRCM_I2CCLK_MGT (0x020) +#define PRCM_SDMMCCLK_MGT (0x024) +#define PRCM_SLIMCLK_MGT (0x028) +#define PRCM_PER1CLK_MGT (0x02C) +#define PRCM_PER2CLK_MGT (0x030) +#define PRCM_PER3CLK_MGT (0x034) +#define PRCM_PER5CLK_MGT (0x038) +#define PRCM_PER6CLK_MGT (0x03C) +#define PRCM_PER7CLK_MGT (0x040) +#define PRCM_LCDCLK_MGT (0x044) +#define PRCM_BMLCLK_MGT (0x04C) +#define PRCM_HSITXCLK_MGT (0x050) +#define PRCM_HSIRXCLK_MGT (0x054) +#define PRCM_HDMICLK_MGT (0x058) +#define PRCM_APEATCLK_MGT (0x05C) +#define PRCM_APETRACECLK_MGT (0x060) +#define PRCM_MCDECLK_MGT (0x064) +#define PRCM_IPI2CCLK_MGT (0x068) +#define PRCM_DSIALTCLK_MGT (0x06C) +#define PRCM_DMACLK_MGT (0x074) +#define PRCM_B2R2CLK_MGT (0x078) +#define PRCM_TVCLK_MGT (0x07C) +#define PRCM_UNIPROCLK_MGT (0x278) +#define PRCM_SSPCLK_MGT (0x280) +#define PRCM_RNGCLK_MGT (0x284) +#define PRCM_UICCCLK_MGT (0x27C) +#define PRCM_MSP1CLK_MGT (0x288) + +#define PRCM_ARM_PLLDIVPS (prcmu_base + 0x118) #define PRCM_ARM_PLLDIVPS_ARM_BRM_RATE 0x3f #define PRCM_ARM_PLLDIVPS_MAX_MASK 0xf -#define PRCM_PLLARM_LOCKP (_PRCMU_BASE + 0x0a8) +#define PRCM_PLLARM_LOCKP (prcmu_base + 0x0a8) #define PRCM_PLLARM_LOCKP_PRCM_PLLARM_LOCKP3 0x2 -#define PRCM_ARM_CHGCLKREQ (_PRCMU_BASE + 0x114) +#define PRCM_ARM_CHGCLKREQ (prcmu_base + 0x114) #define PRCM_ARM_CHGCLKREQ_PRCM_ARM_CHGCLKREQ BIT(0) #define PRCM_ARM_CHGCLKREQ_PRCM_ARM_DIVSEL BIT(16) -#define PRCM_PLLARM_ENABLE (_PRCMU_BASE + 0x98) +#define PRCM_PLLARM_ENABLE (prcmu_base + 0x98) #define PRCM_PLLARM_ENABLE_PRCM_PLLARM_ENABLE 0x1 #define PRCM_PLLARM_ENABLE_PRCM_PLLARM_COUNTON 0x100 -#define PRCM_ARMCLKFIX_MGT (_PRCMU_BASE + 0x0) -#define PRCM_A9PL_FORCE_CLKEN (_PRCMU_BASE + 0x19C) -#define PRCM_A9_RESETN_CLR (_PRCMU_BASE + 0x1f4) -#define PRCM_A9_RESETN_SET (_PRCMU_BASE + 0x1f0) -#define PRCM_ARM_LS_CLAMP (_PRCMU_BASE + 0x30c) -#define PRCM_SRAM_A9 (_PRCMU_BASE + 0x308) +#define PRCM_ARMCLKFIX_MGT (prcmu_base + 0x0) +#define PRCM_A9PL_FORCE_CLKEN (prcmu_base + 0x19C) +#define PRCM_A9_RESETN_CLR (prcmu_base + 0x1f4) +#define PRCM_A9_RESETN_SET (prcmu_base + 0x1f0) +#define PRCM_ARM_LS_CLAMP (prcmu_base + 0x30c) +#define PRCM_SRAM_A9 (prcmu_base + 0x308) #define PRCM_A9PL_FORCE_CLKEN_PRCM_A9PL_FORCE_CLKEN BIT(0) #define PRCM_A9PL_FORCE_CLKEN_PRCM_A9AXI_FORCE_CLKEN BIT(1) -/* ARM WFI Standby signal register */ -#define PRCM_ARM_WFI_STANDBY (_PRCMU_BASE + 0x130) -#define PRCM_ARM_WFI_STANDBY_WFI0 0x08 -#define PRCM_ARM_WFI_STANDBY_WFI1 0x10 -#define PRCM_IOCR (_PRCMU_BASE + 0x310) -#define PRCM_IOCR_IOFORCE 0x1 - /* CPU mailbox registers */ -#define PRCM_MBOX_CPU_VAL (_PRCMU_BASE + 0x0fc) -#define PRCM_MBOX_CPU_SET (_PRCMU_BASE + 0x100) -#define PRCM_MBOX_CPU_CLR (_PRCMU_BASE + 0x104) - -/* Dual A9 core interrupt management unit registers */ -#define PRCM_A9_MASK_REQ (_PRCMU_BASE + 0x328) -#define PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ 0x1 - -#define PRCM_A9_MASK_ACK (_PRCMU_BASE + 0x32c) -#define PRCM_ARMITMSK31TO0 (_PRCMU_BASE + 0x11c) -#define PRCM_ARMITMSK63TO32 (_PRCMU_BASE + 0x120) -#define PRCM_ARMITMSK95TO64 (_PRCMU_BASE + 0x124) -#define PRCM_ARMITMSK127TO96 (_PRCMU_BASE + 0x128) -#define PRCM_POWER_STATE_VAL (_PRCMU_BASE + 0x25C) -#define PRCM_ARMITVAL31TO0 (_PRCMU_BASE + 0x260) -#define PRCM_ARMITVAL63TO32 (_PRCMU_BASE + 0x264) -#define PRCM_ARMITVAL95TO64 (_PRCMU_BASE + 0x268) -#define PRCM_ARMITVAL127TO96 (_PRCMU_BASE + 0x26C) - -#define PRCM_HOSTACCESS_REQ (_PRCMU_BASE + 0x334) +#define PRCM_MBOX_CPU_VAL (prcmu_base + 0x0fc) +#define PRCM_MBOX_CPU_SET (prcmu_base + 0x100) +#define PRCM_MBOX_CPU_CLR (prcmu_base + 0x104) + +#define PRCM_HOSTACCESS_REQ (prcmu_base + 0x334) #define PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ 0x1 #define PRCM_HOSTACCESS_REQ_WAKE_REQ BIT(16) #define ARM_WAKEUP_MODEM 0x1 -#define PRCM_ARM_IT1_CLR (_PRCMU_BASE + 0x48C) -#define PRCM_ARM_IT1_VAL (_PRCMU_BASE + 0x494) -#define PRCM_HOLD_EVT (_PRCMU_BASE + 0x174) +#define PRCM_ARM_IT1_CLR (prcmu_base + 0x48C) +#define PRCM_ARM_IT1_VAL (prcmu_base + 0x494) +#define PRCM_HOLD_EVT (prcmu_base + 0x174) -#define PRCM_MOD_AWAKE_STATUS (_PRCMU_BASE + 0x4A0) +#define PRCM_MOD_AWAKE_STATUS (prcmu_base + 0x4A0) #define PRCM_MOD_AWAKE_STATUS_PRCM_MOD_COREPD_AWAKE BIT(0) #define PRCM_MOD_AWAKE_STATUS_PRCM_MOD_AAPD_AWAKE BIT(1) #define PRCM_MOD_AWAKE_STATUS_PRCM_MOD_VMODEM_OFF_ISO BIT(2) -#define PRCM_ITSTATUS0 (_PRCMU_BASE + 0x148) -#define PRCM_ITSTATUS1 (_PRCMU_BASE + 0x150) -#define PRCM_ITSTATUS2 (_PRCMU_BASE + 0x158) -#define PRCM_ITSTATUS3 (_PRCMU_BASE + 0x160) -#define PRCM_ITSTATUS4 (_PRCMU_BASE + 0x168) -#define PRCM_ITSTATUS5 (_PRCMU_BASE + 0x484) -#define PRCM_ITCLEAR5 (_PRCMU_BASE + 0x488) -#define PRCM_ARMIT_MASKXP70_IT (_PRCMU_BASE + 0x1018) +#define PRCM_ITSTATUS0 (prcmu_base + 0x148) +#define PRCM_ITSTATUS1 (prcmu_base + 0x150) +#define PRCM_ITSTATUS2 (prcmu_base + 0x158) +#define PRCM_ITSTATUS3 (prcmu_base + 0x160) +#define PRCM_ITSTATUS4 (prcmu_base + 0x168) +#define PRCM_ITSTATUS5 (prcmu_base + 0x484) +#define PRCM_ITCLEAR5 (prcmu_base + 0x488) +#define PRCM_ARMIT_MASKXP70_IT (prcmu_base + 0x1018) /* System reset register */ -#define PRCM_APE_SOFTRST (_PRCMU_BASE + 0x228) +#define PRCM_APE_SOFTRST (prcmu_base + 0x228) /* Level shifter and clamp control registers */ -#define PRCM_MMIP_LS_CLAMP_SET (_PRCMU_BASE + 0x420) -#define PRCM_MMIP_LS_CLAMP_CLR (_PRCMU_BASE + 0x424) +#define PRCM_MMIP_LS_CLAMP_SET (prcmu_base + 0x420) +#define PRCM_MMIP_LS_CLAMP_CLR (prcmu_base + 0x424) #define PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMP BIT(11) #define PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMPI BIT(22) /* PRCMU clock/PLL/reset registers */ -#define PRCM_PLLSOC0_FREQ (_PRCMU_BASE + 0x080) -#define PRCM_PLLSOC1_FREQ (_PRCMU_BASE + 0x084) -#define PRCM_PLLARM_FREQ (_PRCMU_BASE + 0x088) -#define PRCM_PLLDDR_FREQ (_PRCMU_BASE + 0x08C) +#define PRCM_PLLSOC0_FREQ (prcmu_base + 0x080) +#define PRCM_PLLSOC1_FREQ (prcmu_base + 0x084) +#define PRCM_PLLARM_FREQ (prcmu_base + 0x088) +#define PRCM_PLLDDR_FREQ (prcmu_base + 0x08C) #define PRCM_PLL_FREQ_D_SHIFT 0 #define PRCM_PLL_FREQ_D_MASK BITS(0, 7) #define PRCM_PLL_FREQ_N_SHIFT 8 @@ -152,14 +126,14 @@ #define PRCM_PLL_FREQ_SELDIV2 BIT(24) #define PRCM_PLL_FREQ_DIV2EN BIT(25) -#define PRCM_PLLDSI_FREQ (_PRCMU_BASE + 0x500) -#define PRCM_PLLDSI_ENABLE (_PRCMU_BASE + 0x504) -#define PRCM_PLLDSI_LOCKP (_PRCMU_BASE + 0x508) -#define PRCM_DSI_PLLOUT_SEL (_PRCMU_BASE + 0x530) -#define PRCM_DSITVCLK_DIV (_PRCMU_BASE + 0x52C) -#define PRCM_PLLDSI_LOCKP (_PRCMU_BASE + 0x508) -#define PRCM_APE_RESETN_SET (_PRCMU_BASE + 0x1E4) -#define PRCM_APE_RESETN_CLR (_PRCMU_BASE + 0x1E8) +#define PRCM_PLLDSI_FREQ (prcmu_base + 0x500) +#define PRCM_PLLDSI_ENABLE (prcmu_base + 0x504) +#define PRCM_PLLDSI_LOCKP (prcmu_base + 0x508) +#define PRCM_DSI_PLLOUT_SEL (prcmu_base + 0x530) +#define PRCM_DSITVCLK_DIV (prcmu_base + 0x52C) +#define PRCM_PLLDSI_LOCKP (prcmu_base + 0x508) +#define PRCM_APE_RESETN_SET (prcmu_base + 0x1E4) +#define PRCM_APE_RESETN_CLR (prcmu_base + 0x1E8) #define PRCM_PLLDSI_ENABLE_PRCM_PLLDSI_ENABLE BIT(0) @@ -188,30 +162,30 @@ #define PRCM_APE_RESETN_DSIPLL_RESETN BIT(14) -#define PRCM_CLKOCR (_PRCMU_BASE + 0x1CC) +#define PRCM_CLKOCR (prcmu_base + 0x1CC) #define PRCM_CLKOCR_CLKOUT0_REF_CLK (1 << 0) #define PRCM_CLKOCR_CLKOUT0_MASK BITS(0, 13) #define PRCM_CLKOCR_CLKOUT1_REF_CLK (1 << 16) #define PRCM_CLKOCR_CLKOUT1_MASK BITS(16, 29) /* ePOD and memory power signal control registers */ -#define PRCM_EPOD_C_SET (_PRCMU_BASE + 0x410) -#define PRCM_SRAM_LS_SLEEP (_PRCMU_BASE + 0x304) +#define PRCM_EPOD_C_SET (prcmu_base + 0x410) +#define PRCM_SRAM_LS_SLEEP (prcmu_base + 0x304) /* Debug power control unit registers */ -#define PRCM_POWER_STATE_SET (_PRCMU_BASE + 0x254) +#define PRCM_POWER_STATE_SET (prcmu_base + 0x254) /* Miscellaneous unit registers */ -#define PRCM_DSI_SW_RESET (_PRCMU_BASE + 0x324) -#define PRCM_GPIOCR (_PRCMU_BASE + 0x138) +#define PRCM_DSI_SW_RESET (prcmu_base + 0x324) +#define PRCM_GPIOCR (prcmu_base + 0x138) #define PRCM_GPIOCR_DBG_STM_MOD_CMD1 0x800 #define PRCM_GPIOCR_DBG_UARTMOD_CMD0 0x1 /* PRCMU HW semaphore */ -#define PRCM_SEM (_PRCMU_BASE + 0x400) +#define PRCM_SEM (prcmu_base + 0x400) #define PRCM_SEM_PRCM_SEM BIT(0) -#define PRCM_TCR (_PRCMU_BASE + 0x1C8) +#define PRCM_TCR (prcmu_base + 0x1C8) #define PRCM_TCR_TENSEL_MASK BITS(0, 7) #define PRCM_TCR_STOP_TIMERS BIT(16) #define PRCM_TCR_DOZE_MODE BIT(17) @@ -239,15 +213,15 @@ /* GPIOCR register */ #define PRCM_GPIOCR_SPI2_SELECT BIT(23) -#define PRCM_DDR_SUBSYS_APE_MINBW (_PRCMU_BASE + 0x438) -#define PRCM_CGATING_BYPASS (_PRCMU_BASE + 0x134) +#define PRCM_DDR_SUBSYS_APE_MINBW (prcmu_base + 0x438) +#define PRCM_CGATING_BYPASS (prcmu_base + 0x134) #define PRCM_CGATING_BYPASS_ICN2 BIT(6) /* Miscellaneous unit registers */ -#define PRCM_RESOUTN_SET (_PRCMU_BASE + 0x214) -#define PRCM_RESOUTN_CLR (_PRCMU_BASE + 0x218) +#define PRCM_RESOUTN_SET (prcmu_base + 0x214) +#define PRCM_RESOUTN_CLR (prcmu_base + 0x218) /* System reset register */ -#define PRCM_APE_SOFTRST (_PRCMU_BASE + 0x228) +#define PRCM_APE_SOFTRST (prcmu_base + 0x228) #endif /* __DB8500_PRCMU_REGS_H */ diff --git a/drivers/mfd/ezx-pcap.c b/drivers/mfd/ezx-pcap.c index b7a61f0f27a4..5502106ad515 100644 --- a/drivers/mfd/ezx-pcap.c +++ b/drivers/mfd/ezx-pcap.c @@ -393,7 +393,7 @@ static int pcap_add_subdev(struct pcap_chip *pcap, static int ezx_pcap_remove(struct spi_device *spi) { - struct pcap_chip *pcap = dev_get_drvdata(&spi->dev); + struct pcap_chip *pcap = spi_get_drvdata(spi); struct pcap_platform_data *pdata = spi->dev.platform_data; int i, adc_irq; @@ -403,7 +403,7 @@ static int ezx_pcap_remove(struct spi_device *spi) /* cleanup ADC */ adc_irq = pcap_to_irq(pcap, (pdata->config & PCAP_SECOND_PORT) ? PCAP_IRQ_ADCDONE2 : PCAP_IRQ_ADCDONE); - free_irq(adc_irq, pcap); + devm_free_irq(&spi->dev, adc_irq, pcap); mutex_lock(&pcap->adc_mutex); for (i = 0; i < PCAP_ADC_MAXQ; i++) kfree(pcap->adc_queue[i]); @@ -415,8 +415,6 @@ static int ezx_pcap_remove(struct spi_device *spi) destroy_workqueue(pcap->workqueue); - kfree(pcap); - return 0; } @@ -431,7 +429,7 @@ static int ezx_pcap_probe(struct spi_device *spi) if (!pdata) goto ret; - pcap = kzalloc(sizeof(*pcap), GFP_KERNEL); + pcap = devm_kzalloc(&spi->dev, sizeof(*pcap), GFP_KERNEL); if (!pcap) { ret = -ENOMEM; goto ret; @@ -441,14 +439,14 @@ static int ezx_pcap_probe(struct spi_device *spi) 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); + spi_set_drvdata(spi, 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; + goto ret; pcap->spi = spi; @@ -458,7 +456,7 @@ static int ezx_pcap_probe(struct spi_device *spi) if (!pcap->workqueue) { ret = -ENOMEM; dev_err(&spi->dev, "can't create pcap thread\n"); - goto free_pcap; + goto ret; } /* redirect interrupts to AP, except adcdone2 */ @@ -491,7 +489,8 @@ static int ezx_pcap_probe(struct spi_device *spi) 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); + ret = devm_request_irq(&spi->dev, adc_irq, pcap_adc_irq, 0, "ADC", + pcap); if (ret) goto free_irqchip; @@ -511,14 +510,12 @@ static int ezx_pcap_probe(struct spi_device *spi) remove_subdevs: device_for_each_child(&spi->dev, NULL, pcap_remove_subdev); /* free_adc: */ - free_irq(adc_irq, pcap); + devm_free_irq(&spi->dev, adc_irq, pcap); free_irqchip: for (i = pcap->irq_base; i < (pcap->irq_base + PCAP_NIRQS); i++) irq_set_chip_and_handler(i, NULL, NULL); /* destroy_workqueue: */ destroy_workqueue(pcap->workqueue); -free_pcap: - kfree(pcap); ret: return ret; } diff --git a/drivers/mfd/htc-pasic3.c b/drivers/mfd/htc-pasic3.c index 9e5453d21a68..0285fceb99a6 100644 --- a/drivers/mfd/htc-pasic3.c +++ b/drivers/mfd/htc-pasic3.c @@ -208,18 +208,7 @@ static struct platform_driver pasic3_driver = { .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_platform_driver_probe(pasic3_driver, pasic3_probe); MODULE_AUTHOR("Philipp Zabel <philipp.zabel@gmail.com>"); MODULE_DESCRIPTION("Core driver for HTC PASIC3"); diff --git a/drivers/mfd/intel_msic.c b/drivers/mfd/intel_msic.c index 1804331bd52c..5be3b5e13855 100644 --- a/drivers/mfd/intel_msic.c +++ b/drivers/mfd/intel_msic.c @@ -323,7 +323,8 @@ static int intel_msic_init_devices(struct intel_msic *msic) if (pdata->ocd) { unsigned gpio = pdata->ocd->gpio; - ret = gpio_request_one(gpio, GPIOF_IN, "ocd_gpio"); + ret = devm_gpio_request_one(&pdev->dev, gpio, + GPIOF_IN, "ocd_gpio"); if (ret) { dev_err(&pdev->dev, "failed to register OCD GPIO\n"); return ret; @@ -332,7 +333,6 @@ static int intel_msic_init_devices(struct intel_msic *msic) ret = gpio_to_irq(gpio); if (ret < 0) { dev_err(&pdev->dev, "no IRQ number for OCD GPIO\n"); - gpio_free(gpio); return ret; } @@ -359,8 +359,6 @@ static int intel_msic_init_devices(struct intel_msic *msic) fail: mfd_remove_devices(&pdev->dev); - if (pdata->ocd) - gpio_free(pdata->ocd->gpio); return ret; } @@ -368,12 +366,8 @@ fail: static void intel_msic_remove_devices(struct intel_msic *msic) { struct platform_device *pdev = msic->pdev; - struct intel_msic_platform_data *pdata = pdev->dev.platform_data; mfd_remove_devices(&pdev->dev); - - if (pdata->ocd) - gpio_free(pdata->ocd->gpio); } static int intel_msic_probe(struct platform_device *pdev) diff --git a/drivers/mfd/lm3533-core.c b/drivers/mfd/lm3533-core.c index ceebf2c1ea97..4b7e6dac1de8 100644 --- a/drivers/mfd/lm3533-core.c +++ b/drivers/mfd/lm3533-core.c @@ -496,8 +496,8 @@ static int lm3533_device_init(struct lm3533 *lm3533) dev_set_drvdata(lm3533->dev, lm3533); if (gpio_is_valid(lm3533->gpio_hwen)) { - ret = gpio_request_one(lm3533->gpio_hwen, GPIOF_OUT_INIT_LOW, - "lm3533-hwen"); + ret = devm_gpio_request_one(lm3533->dev, lm3533->gpio_hwen, + GPIOF_OUT_INIT_LOW, "lm3533-hwen"); if (ret < 0) { dev_err(lm3533->dev, "failed to request HWEN GPIO %d\n", @@ -528,8 +528,6 @@ err_unregister: mfd_remove_devices(lm3533->dev); err_disable: lm3533_disable(lm3533); - if (gpio_is_valid(lm3533->gpio_hwen)) - gpio_free(lm3533->gpio_hwen); return ret; } @@ -542,8 +540,6 @@ static void lm3533_device_exit(struct lm3533 *lm3533) mfd_remove_devices(lm3533->dev); lm3533_disable(lm3533); - if (gpio_is_valid(lm3533->gpio_hwen)) - gpio_free(lm3533->gpio_hwen); } static bool lm3533_readable_register(struct device *dev, unsigned int reg) diff --git a/drivers/mfd/max77686.c b/drivers/mfd/max77686.c index 4d73963cd8f0..1cbb17609c8b 100644 --- a/drivers/mfd/max77686.c +++ b/drivers/mfd/max77686.c @@ -46,7 +46,7 @@ static struct regmap_config max77686_regmap_config = { #ifdef CONFIG_OF static struct of_device_id max77686_pmic_dt_match[] = { - {.compatible = "maxim,max77686", .data = 0}, + {.compatible = "maxim,max77686", .data = NULL}, {}, }; diff --git a/drivers/mfd/mc13xxx-spi.c b/drivers/mfd/mc13xxx-spi.c index 3032bae20b62..77189daadf1e 100644 --- a/drivers/mfd/mc13xxx-spi.c +++ b/drivers/mfd/mc13xxx-spi.c @@ -131,7 +131,7 @@ static int mc13xxx_spi_probe(struct spi_device *spi) if (!mc13xxx) return -ENOMEM; - dev_set_drvdata(&spi->dev, mc13xxx); + spi_set_drvdata(spi, mc13xxx); spi->mode = SPI_MODE_0 | SPI_CS_HIGH; mc13xxx->dev = &spi->dev; @@ -144,7 +144,7 @@ static int mc13xxx_spi_probe(struct spi_device *spi) ret = PTR_ERR(mc13xxx->regmap); dev_err(mc13xxx->dev, "Failed to initialize register map: %d\n", ret); - dev_set_drvdata(&spi->dev, NULL); + spi_set_drvdata(spi, NULL); return ret; } @@ -164,7 +164,7 @@ static int mc13xxx_spi_probe(struct spi_device *spi) static int mc13xxx_spi_remove(struct spi_device *spi) { - struct mc13xxx *mc13xxx = dev_get_drvdata(&spi->dev); + struct mc13xxx *mc13xxx = spi_get_drvdata(spi); mc13xxx_common_cleanup(mc13xxx); diff --git a/drivers/mfd/omap-usb-host.c b/drivers/mfd/omap-usb-host.c index 4febc5c7fdee..759fae3ca7fb 100644 --- a/drivers/mfd/omap-usb-host.c +++ b/drivers/mfd/omap-usb-host.c @@ -1,8 +1,9 @@ /** * omap-usb-host.c - The USBHS core driver for OMAP EHCI & OHCI * - * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com + * Copyright (C) 2011-2013 Texas Instruments Incorporated - http://www.ti.com * Author: Keshava Munegowda <keshava_mgowda@ti.com> + * Author: Roger Quadros <rogerq@ti.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 of @@ -27,6 +28,9 @@ #include <linux/platform_device.h> #include <linux/platform_data/usb-omap.h> #include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/err.h> #include "omap-usb.h" @@ -137,6 +141,49 @@ static inline u8 usbhs_readb(void __iomem *base, u8 reg) /*-------------------------------------------------------------------------*/ +/** + * Map 'enum usbhs_omap_port_mode' found in <linux/platform_data/usb-omap.h> + * to the device tree binding portN-mode found in + * 'Documentation/devicetree/bindings/mfd/omap-usb-host.txt' + */ +static const char * const port_modes[] = { + [OMAP_USBHS_PORT_MODE_UNUSED] = "", + [OMAP_EHCI_PORT_MODE_PHY] = "ehci-phy", + [OMAP_EHCI_PORT_MODE_TLL] = "ehci-tll", + [OMAP_EHCI_PORT_MODE_HSIC] = "ehci-hsic", + [OMAP_OHCI_PORT_MODE_PHY_6PIN_DATSE0] = "ohci-phy-6pin-datse0", + [OMAP_OHCI_PORT_MODE_PHY_6PIN_DPDM] = "ohci-phy-6pin-dpdm", + [OMAP_OHCI_PORT_MODE_PHY_3PIN_DATSE0] = "ohci-phy-3pin-datse0", + [OMAP_OHCI_PORT_MODE_PHY_4PIN_DPDM] = "ohci-phy-4pin-dpdm", + [OMAP_OHCI_PORT_MODE_TLL_6PIN_DATSE0] = "ohci-tll-6pin-datse0", + [OMAP_OHCI_PORT_MODE_TLL_6PIN_DPDM] = "ohci-tll-6pin-dpdm", + [OMAP_OHCI_PORT_MODE_TLL_3PIN_DATSE0] = "ohci-tll-3pin-datse0", + [OMAP_OHCI_PORT_MODE_TLL_4PIN_DPDM] = "ohci-tll-4pin-dpdm", + [OMAP_OHCI_PORT_MODE_TLL_2PIN_DATSE0] = "ohci-tll-2pin-datse0", + [OMAP_OHCI_PORT_MODE_TLL_2PIN_DPDM] = "ohci-tll-2pin-dpdm", +}; + +/** + * omap_usbhs_get_dt_port_mode - Get the 'enum usbhs_omap_port_mode' + * from the port mode string. + * @mode: The port mode string, usually obtained from device tree. + * + * The function returns the 'enum usbhs_omap_port_mode' that matches the + * provided port mode string as per the port_modes table. + * If no match is found it returns -ENODEV + */ +static const int omap_usbhs_get_dt_port_mode(const char *mode) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(port_modes); i++) { + if (!strcmp(mode, port_modes[i])) + return i; + } + + return -ENODEV; +} + static struct platform_device *omap_usbhs_alloc_child(const char *name, struct resource *res, int num_resources, void *pdata, size_t pdata_size, struct device *dev) @@ -278,7 +325,7 @@ static int usbhs_runtime_resume(struct device *dev) dev_dbg(dev, "usbhs_runtime_resume\n"); - omap_tll_enable(); + omap_tll_enable(pdata); if (!IS_ERR(omap->ehci_logic_fck)) clk_enable(omap->ehci_logic_fck); @@ -353,7 +400,7 @@ static int usbhs_runtime_suspend(struct device *dev) if (!IS_ERR(omap->ehci_logic_fck)) clk_disable(omap->ehci_logic_fck); - omap_tll_disable(); + omap_tll_disable(pdata); return 0; } @@ -430,24 +477,10 @@ static unsigned omap_usbhs_rev2_hostconfig(struct usbhs_hcd_omap *omap, static void omap_usbhs_init(struct device *dev) { struct usbhs_hcd_omap *omap = dev_get_drvdata(dev); - struct usbhs_omap_platform_data *pdata = omap->pdata; unsigned reg; dev_dbg(dev, "starting TI HSUSB Controller\n"); - if (pdata->phy_reset) { - if (gpio_is_valid(pdata->reset_gpio_port[0])) - gpio_request_one(pdata->reset_gpio_port[0], - GPIOF_OUT_INIT_LOW, "USB1 PHY reset"); - - if (gpio_is_valid(pdata->reset_gpio_port[1])) - gpio_request_one(pdata->reset_gpio_port[1], - GPIOF_OUT_INIT_LOW, "USB2 PHY reset"); - - /* Hold the PHY in RESET for enough time till DIR is high */ - udelay(10); - } - pm_runtime_get_sync(dev); reg = usbhs_read(omap->uhh_base, OMAP_UHH_HOSTCONFIG); @@ -476,36 +509,59 @@ static void omap_usbhs_init(struct device *dev) dev_dbg(dev, "UHH setup done, uhh_hostconfig=%x\n", reg); pm_runtime_put_sync(dev); - if (pdata->phy_reset) { - /* Hold the PHY in RESET for enough time till - * PHY is settled and ready - */ - udelay(10); +} + +static int usbhs_omap_get_dt_pdata(struct device *dev, + struct usbhs_omap_platform_data *pdata) +{ + int ret, i; + struct device_node *node = dev->of_node; - if (gpio_is_valid(pdata->reset_gpio_port[0])) - gpio_set_value_cansleep - (pdata->reset_gpio_port[0], 1); + ret = of_property_read_u32(node, "num-ports", &pdata->nports); + if (ret) + pdata->nports = 0; - if (gpio_is_valid(pdata->reset_gpio_port[1])) - gpio_set_value_cansleep - (pdata->reset_gpio_port[1], 1); + if (pdata->nports > OMAP3_HS_USB_PORTS) { + dev_warn(dev, "Too many num_ports <%d> in device tree. Max %d\n", + pdata->nports, OMAP3_HS_USB_PORTS); + return -ENODEV; } -} -static void omap_usbhs_deinit(struct device *dev) -{ - struct usbhs_hcd_omap *omap = dev_get_drvdata(dev); - struct usbhs_omap_platform_data *pdata = omap->pdata; + /* get port modes */ + for (i = 0; i < OMAP3_HS_USB_PORTS; i++) { + char prop[11]; + const char *mode; - if (pdata->phy_reset) { - if (gpio_is_valid(pdata->reset_gpio_port[0])) - gpio_free(pdata->reset_gpio_port[0]); + pdata->port_mode[i] = OMAP_USBHS_PORT_MODE_UNUSED; + + snprintf(prop, sizeof(prop), "port%d-mode", i + 1); + ret = of_property_read_string(node, prop, &mode); + if (ret < 0) + continue; + + ret = omap_usbhs_get_dt_port_mode(mode); + if (ret < 0) { + dev_warn(dev, "Invalid port%d-mode \"%s\" in device tree\n", + i, mode); + return -ENODEV; + } - if (gpio_is_valid(pdata->reset_gpio_port[1])) - gpio_free(pdata->reset_gpio_port[1]); + dev_dbg(dev, "port%d-mode: %s -> %d\n", i, mode, ret); + pdata->port_mode[i] = ret; } + + /* get flags */ + pdata->single_ulpi_bypass = of_property_read_bool(node, + "single-ulpi-bypass"); + + return 0; } +static struct of_device_id usbhs_child_match_table[] = { + { .compatible = "ti,omap-ehci", }, + { .compatible = "ti,omap-ohci", }, + { } +}; /** * usbhs_omap_probe - initialize TI-based HCDs @@ -522,26 +578,46 @@ static int usbhs_omap_probe(struct platform_device *pdev) int i; bool need_logic_fck; + if (dev->of_node) { + /* For DT boot we populate platform data from OF node */ + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + ret = usbhs_omap_get_dt_pdata(dev, pdata); + if (ret) + return ret; + + dev->platform_data = pdata; + } + if (!pdata) { dev_err(dev, "Missing platform data\n"); return -ENODEV; } + if (pdata->nports > OMAP3_HS_USB_PORTS) { + dev_info(dev, "Too many num_ports <%d> in platform_data. Max %d\n", + pdata->nports, OMAP3_HS_USB_PORTS); + return -ENODEV; + } + omap = devm_kzalloc(dev, sizeof(*omap), GFP_KERNEL); if (!omap) { dev_err(dev, "Memory allocation failed\n"); return -ENOMEM; } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "uhh"); - omap->uhh_base = devm_request_and_ioremap(dev, res); - if (!omap->uhh_base) { - dev_err(dev, "Resource request/ioremap failed\n"); - return -EADDRNOTAVAIL; - } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + omap->uhh_base = devm_ioremap_resource(dev, res); + if (IS_ERR(omap->uhh_base)) + return PTR_ERR(omap->uhh_base); omap->pdata = pdata; + /* Initialize the TLL subsystem */ + omap_tll_init(pdata); + pm_runtime_enable(dev); platform_set_drvdata(pdev, omap); @@ -575,6 +651,7 @@ static int usbhs_omap_probe(struct platform_device *pdev) omap->usbhs_rev, omap->nports); break; } + pdata->nports = omap->nports; } i = sizeof(struct clk *) * omap->nports; @@ -700,17 +777,28 @@ static int usbhs_omap_probe(struct platform_device *pdev) } omap_usbhs_init(dev); - ret = omap_usbhs_alloc_children(pdev); - if (ret) { - dev_err(dev, "omap_usbhs_alloc_children failed\n"); - goto err_alloc; + + if (dev->of_node) { + ret = of_platform_populate(dev->of_node, + usbhs_child_match_table, NULL, dev); + + if (ret) { + dev_err(dev, "Failed to create DT children: %d\n", ret); + goto err_alloc; + } + + } else { + ret = omap_usbhs_alloc_children(pdev); + if (ret) { + dev_err(dev, "omap_usbhs_alloc_children failed: %d\n", + ret); + goto err_alloc; + } } return 0; err_alloc: - omap_usbhs_deinit(&pdev->dev); - for (i = 0; i < omap->nports; i++) { if (!IS_ERR(omap->utmi_clk[i])) clk_put(omap->utmi_clk[i]); @@ -744,6 +832,13 @@ err_mem: return ret; } +static int usbhs_omap_remove_child(struct device *dev, void *data) +{ + dev_info(dev, "unregistering\n"); + platform_device_unregister(to_platform_device(dev)); + return 0; +} + /** * usbhs_omap_remove - shutdown processing for UHH & TLL HCDs * @pdev: USB Host Controller being removed @@ -755,8 +850,6 @@ static int usbhs_omap_remove(struct platform_device *pdev) struct usbhs_hcd_omap *omap = platform_get_drvdata(pdev); int i; - omap_usbhs_deinit(&pdev->dev); - for (i = 0; i < omap->nports; i++) { if (!IS_ERR(omap->utmi_clk[i])) clk_put(omap->utmi_clk[i]); @@ -777,6 +870,8 @@ static int usbhs_omap_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); + /* remove children */ + device_for_each_child(&pdev->dev, NULL, usbhs_omap_remove_child); return 0; } @@ -785,16 +880,26 @@ static const struct dev_pm_ops usbhsomap_dev_pm_ops = { .runtime_resume = usbhs_runtime_resume, }; +static const struct of_device_id usbhs_omap_dt_ids[] = { + { .compatible = "ti,usbhs-host" }, + { } +}; + +MODULE_DEVICE_TABLE(of, usbhs_omap_dt_ids); + + static struct platform_driver usbhs_omap_driver = { .driver = { .name = (char *)usbhs_driver_name, .owner = THIS_MODULE, .pm = &usbhsomap_dev_pm_ops, + .of_match_table = of_match_ptr(usbhs_omap_dt_ids), }, .remove = usbhs_omap_remove, }; MODULE_AUTHOR("Keshava Munegowda <keshava_mgowda@ti.com>"); +MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); MODULE_ALIAS("platform:" USBHS_DRIVER_NAME); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("usb host common core driver for omap EHCI and OHCI"); diff --git a/drivers/mfd/omap-usb-tll.c b/drivers/mfd/omap-usb-tll.c index 0aef1a768880..e59ac4cbac96 100644 --- a/drivers/mfd/omap-usb-tll.c +++ b/drivers/mfd/omap-usb-tll.c @@ -1,8 +1,9 @@ /** * omap-usb-tll.c - The USB TLL driver for OMAP EHCI & OHCI * - * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com + * Copyright (C) 2012-2013 Texas Instruments Incorporated - http://www.ti.com * Author: Keshava Munegowda <keshava_mgowda@ti.com> + * Author: Roger Quadros <rogerq@ti.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 of @@ -27,6 +28,7 @@ #include <linux/err.h> #include <linux/pm_runtime.h> #include <linux/platform_data/usb-omap.h> +#include <linux/of.h> #define USBTLL_DRIVER_NAME "usbhs_tll" @@ -105,8 +107,8 @@ struct usbtll_omap { int nch; /* num. of channels */ - struct usbhs_omap_platform_data *pdata; struct clk **ch_clk; + void __iomem *base; }; /*-------------------------------------------------------------------------*/ @@ -210,14 +212,10 @@ static unsigned ohci_omap3_fslsmode(enum usbhs_omap_port_mode mode) static int usbtll_omap_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct usbhs_omap_platform_data *pdata = dev->platform_data; - void __iomem *base; struct resource *res; struct usbtll_omap *tll; - unsigned reg; int ret = 0; int i, ver; - bool needs_tll; dev_dbg(dev, "starting TI HSUSB TLL Controller\n"); @@ -227,26 +225,16 @@ static int usbtll_omap_probe(struct platform_device *pdev) return -ENOMEM; } - if (!pdata) { - dev_err(dev, "Platform data missing\n"); - return -ENODEV; - } - - tll->pdata = pdata; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_request_and_ioremap(dev, res); - if (!base) { - ret = -EADDRNOTAVAIL; - dev_err(dev, "Resource request/ioremap failed:%d\n", ret); - return ret; - } + tll->base = devm_ioremap_resource(dev, res); + if (IS_ERR(tll->base)) + return PTR_ERR(tll->base); platform_set_drvdata(pdev, tll); pm_runtime_enable(dev); pm_runtime_get_sync(dev); - ver = usbtll_read(base, OMAP_USBTLL_REVISION); + ver = usbtll_read(tll->base, OMAP_USBTLL_REVISION); switch (ver) { case OMAP_USBTLL_REV1: case OMAP_USBTLL_REV4: @@ -283,11 +271,85 @@ static int usbtll_omap_probe(struct platform_device *pdev) dev_dbg(dev, "can't get clock : %s\n", clkname); } + pm_runtime_put_sync(dev); + /* only after this can omap_tll_enable/disable work */ + spin_lock(&tll_lock); + tll_dev = dev; + spin_unlock(&tll_lock); + + return 0; + +err_clk_alloc: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + return ret; +} + +/** + * usbtll_omap_remove - shutdown processing for UHH & TLL HCDs + * @pdev: USB Host Controller being removed + * + * Reverses the effect of usbtll_omap_probe(). + */ +static int usbtll_omap_remove(struct platform_device *pdev) +{ + struct usbtll_omap *tll = platform_get_drvdata(pdev); + int i; + + spin_lock(&tll_lock); + tll_dev = NULL; + spin_unlock(&tll_lock); + + for (i = 0; i < tll->nch; i++) + if (!IS_ERR(tll->ch_clk[i])) + clk_put(tll->ch_clk[i]); + + pm_runtime_disable(&pdev->dev); + return 0; +} + +static const struct of_device_id usbtll_omap_dt_ids[] = { + { .compatible = "ti,usbhs-tll" }, + { } +}; + +MODULE_DEVICE_TABLE(of, usbtll_omap_dt_ids); + +static struct platform_driver usbtll_omap_driver = { + .driver = { + .name = (char *)usbtll_driver_name, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(usbtll_omap_dt_ids), + }, + .probe = usbtll_omap_probe, + .remove = usbtll_omap_remove, +}; + +int omap_tll_init(struct usbhs_omap_platform_data *pdata) +{ + int i; + bool needs_tll; + unsigned reg; + struct usbtll_omap *tll; + + spin_lock(&tll_lock); + + if (!tll_dev) { + spin_unlock(&tll_lock); + return -ENODEV; + } + + tll = dev_get_drvdata(tll_dev); + needs_tll = false; for (i = 0; i < tll->nch; i++) needs_tll |= omap_usb_mode_needs_tll(pdata->port_mode[i]); + pm_runtime_get_sync(tll_dev); + if (needs_tll) { + void __iomem *base = tll->base; /* Program Common TLL register */ reg = usbtll_read(base, OMAP_TLL_SHARED_CONF); @@ -336,51 +398,29 @@ static int usbtll_omap_probe(struct platform_device *pdev) } } - pm_runtime_put_sync(dev); - /* only after this can omap_tll_enable/disable work */ - spin_lock(&tll_lock); - tll_dev = dev; + pm_runtime_put_sync(tll_dev); + spin_unlock(&tll_lock); return 0; - -err_clk_alloc: - pm_runtime_put_sync(dev); - pm_runtime_disable(dev); - - return ret; } +EXPORT_SYMBOL_GPL(omap_tll_init); -/** - * usbtll_omap_remove - shutdown processing for UHH & TLL HCDs - * @pdev: USB Host Controller being removed - * - * Reverses the effect of usbtll_omap_probe(). - */ -static int usbtll_omap_remove(struct platform_device *pdev) +int omap_tll_enable(struct usbhs_omap_platform_data *pdata) { - struct usbtll_omap *tll = platform_get_drvdata(pdev); int i; + struct usbtll_omap *tll; spin_lock(&tll_lock); - tll_dev = NULL; - spin_unlock(&tll_lock); - for (i = 0; i < tll->nch; i++) - if (!IS_ERR(tll->ch_clk[i])) - clk_put(tll->ch_clk[i]); - - pm_runtime_disable(&pdev->dev); - return 0; -} + if (!tll_dev) { + spin_unlock(&tll_lock); + return -ENODEV; + } -static int usbtll_runtime_resume(struct device *dev) -{ - struct usbtll_omap *tll = dev_get_drvdata(dev); - struct usbhs_omap_platform_data *pdata = tll->pdata; - int i; + tll = dev_get_drvdata(tll_dev); - dev_dbg(dev, "usbtll_runtime_resume\n"); + pm_runtime_get_sync(tll_dev); for (i = 0; i < tll->nch; i++) { if (omap_usb_mode_needs_tll(pdata->port_mode[i])) { @@ -391,22 +431,31 @@ static int usbtll_runtime_resume(struct device *dev) r = clk_enable(tll->ch_clk[i]); if (r) { - dev_err(dev, + dev_err(tll_dev, "Error enabling ch %d clock: %d\n", i, r); } } } + spin_unlock(&tll_lock); + return 0; } +EXPORT_SYMBOL_GPL(omap_tll_enable); -static int usbtll_runtime_suspend(struct device *dev) +int omap_tll_disable(struct usbhs_omap_platform_data *pdata) { - struct usbtll_omap *tll = dev_get_drvdata(dev); - struct usbhs_omap_platform_data *pdata = tll->pdata; int i; + struct usbtll_omap *tll; - dev_dbg(dev, "usbtll_runtime_suspend\n"); + spin_lock(&tll_lock); + + if (!tll_dev) { + spin_unlock(&tll_lock); + return -ENODEV; + } + + tll = dev_get_drvdata(tll_dev); for (i = 0; i < tll->nch; i++) { if (omap_usb_mode_needs_tll(pdata->port_mode[i])) { @@ -415,64 +464,16 @@ static int usbtll_runtime_suspend(struct device *dev) } } - return 0; -} - -static const struct dev_pm_ops usbtllomap_dev_pm_ops = { - SET_RUNTIME_PM_OPS(usbtll_runtime_suspend, - usbtll_runtime_resume, - NULL) -}; - -static struct platform_driver usbtll_omap_driver = { - .driver = { - .name = (char *)usbtll_driver_name, - .owner = THIS_MODULE, - .pm = &usbtllomap_dev_pm_ops, - }, - .probe = usbtll_omap_probe, - .remove = usbtll_omap_remove, -}; - -int omap_tll_enable(void) -{ - int ret; - - spin_lock(&tll_lock); - - if (!tll_dev) { - pr_err("%s: OMAP USB TLL not initialized\n", __func__); - ret = -ENODEV; - } else { - ret = pm_runtime_get_sync(tll_dev); - } - - spin_unlock(&tll_lock); - - return ret; -} -EXPORT_SYMBOL_GPL(omap_tll_enable); - -int omap_tll_disable(void) -{ - int ret; - - spin_lock(&tll_lock); - - if (!tll_dev) { - pr_err("%s: OMAP USB TLL not initialized\n", __func__); - ret = -ENODEV; - } else { - ret = pm_runtime_put_sync(tll_dev); - } + pm_runtime_put_sync(tll_dev); spin_unlock(&tll_lock); - return ret; + return 0; } EXPORT_SYMBOL_GPL(omap_tll_disable); MODULE_AUTHOR("Keshava Munegowda <keshava_mgowda@ti.com>"); +MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); MODULE_ALIAS("platform:" USBHS_DRIVER_NAME); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("usb tll driver for TI OMAP EHCI and OHCI controllers"); diff --git a/drivers/mfd/omap-usb.h b/drivers/mfd/omap-usb.h index 972aa961b064..2a508b6aeac8 100644 --- a/drivers/mfd/omap-usb.h +++ b/drivers/mfd/omap-usb.h @@ -1,2 +1,3 @@ -extern int omap_tll_enable(void); -extern int omap_tll_disable(void); +extern int omap_tll_init(struct usbhs_omap_platform_data *pdata); +extern int omap_tll_enable(struct usbhs_omap_platform_data *pdata); +extern int omap_tll_disable(struct usbhs_omap_platform_data *pdata); diff --git a/drivers/mfd/palmas.c b/drivers/mfd/palmas.c index 73bf76df1044..53e9fe638d32 100644 --- a/drivers/mfd/palmas.c +++ b/drivers/mfd/palmas.c @@ -278,20 +278,20 @@ static void palmas_dt_to_pdata(struct i2c_client *i2c, int ret; u32 prop; - ret = of_property_read_u32(node, "ti,mux_pad1", &prop); + ret = of_property_read_u32(node, "ti,mux-pad1", &prop); if (!ret) { pdata->mux_from_pdata = 1; pdata->pad1 = prop; } - ret = of_property_read_u32(node, "ti,mux_pad2", &prop); + ret = of_property_read_u32(node, "ti,mux-pad2", &prop); if (!ret) { pdata->mux_from_pdata = 1; pdata->pad2 = prop; } /* The default for this register is all masked */ - ret = of_property_read_u32(node, "ti,power_ctrl", &prop); + ret = of_property_read_u32(node, "ti,power-ctrl", &prop); if (!ret) pdata->power_ctrl = prop; else @@ -349,6 +349,7 @@ static int palmas_i2c_probe(struct i2c_client *i2c, ret = -ENOMEM; goto err; } + palmas->i2c_clients[i]->dev.of_node = of_node_get(node); } palmas->regmap[i] = devm_regmap_init_i2c(palmas->i2c_clients[i], &palmas_regmap_config[i]); diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/pm8921-core.c index d4b297cbd801..ecc137ffa8c3 100644 --- a/drivers/mfd/pm8921-core.c +++ b/drivers/mfd/pm8921-core.c @@ -17,7 +17,7 @@ #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/err.h> -#include <linux/msm_ssbi.h> +#include <linux/ssbi.h> #include <linux/mfd/core.h> #include <linux/mfd/pm8xxx/pm8921.h> #include <linux/mfd/pm8xxx/core.h> @@ -35,7 +35,7 @@ static int pm8921_readb(const struct device *dev, u16 addr, u8 *val) const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; - return msm_ssbi_read(pmic->dev->parent, addr, val, 1); + return ssbi_read(pmic->dev->parent, addr, val, 1); } static int pm8921_writeb(const struct device *dev, u16 addr, u8 val) @@ -43,7 +43,7 @@ static int pm8921_writeb(const struct device *dev, u16 addr, u8 val) const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; - return msm_ssbi_write(pmic->dev->parent, addr, &val, 1); + return ssbi_write(pmic->dev->parent, addr, &val, 1); } static int pm8921_read_buf(const struct device *dev, u16 addr, u8 *buf, @@ -52,7 +52,7 @@ static int pm8921_read_buf(const struct device *dev, u16 addr, u8 *buf, const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; - return msm_ssbi_read(pmic->dev->parent, addr, buf, cnt); + return ssbi_read(pmic->dev->parent, addr, buf, cnt); } static int pm8921_write_buf(const struct device *dev, u16 addr, u8 *buf, @@ -61,7 +61,7 @@ static int pm8921_write_buf(const struct device *dev, u16 addr, u8 *buf, const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; - return msm_ssbi_write(pmic->dev->parent, addr, buf, cnt); + return ssbi_write(pmic->dev->parent, addr, buf, cnt); } static int pm8921_read_irq_stat(const struct device *dev, int irq) @@ -124,7 +124,7 @@ static int pm8921_probe(struct platform_device *pdev) } /* Read PMIC chip revision */ - rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV, &val, sizeof(val)); + rc = ssbi_read(pdev->dev.parent, REG_HWREV, &val, sizeof(val)); if (rc) { pr_err("Failed to read hw rev reg %d:rc=%d\n", REG_HWREV, rc); goto err_read_rev; @@ -133,7 +133,7 @@ static int pm8921_probe(struct platform_device *pdev) rev = val; /* Read PMIC chip revision 2 */ - rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV_2, &val, sizeof(val)); + rc = ssbi_read(pdev->dev.parent, REG_HWREV_2, &val, sizeof(val)); if (rc) { pr_err("Failed to read hw rev 2 reg %d:rc=%d\n", REG_HWREV_2, rc); diff --git a/drivers/mfd/retu-mfd.c b/drivers/mfd/retu-mfd.c index 3ba048655bf3..a1830986eeb7 100644 --- a/drivers/mfd/retu-mfd.c +++ b/drivers/mfd/retu-mfd.c @@ -1,5 +1,5 @@ /* - * Retu MFD driver + * Retu/Tahvo MFD driver * * Copyright (C) 2004, 2005 Nokia Corporation * @@ -33,7 +33,8 @@ #define RETU_REG_ASICR 0x00 /* ASIC ID and revision */ #define RETU_REG_ASICR_VILMA (1 << 7) /* Bit indicating Vilma */ #define RETU_REG_IDR 0x01 /* Interrupt ID */ -#define RETU_REG_IMR 0x02 /* Interrupt mask */ +#define RETU_REG_IMR 0x02 /* Interrupt mask (Retu) */ +#define TAHVO_REG_IMR 0x03 /* Interrupt mask (Tahvo) */ /* Interrupt sources */ #define RETU_INT_PWR 0 /* Power button */ @@ -84,6 +85,62 @@ static struct regmap_irq_chip retu_irq_chip = { /* Retu device registered for the power off. */ static struct retu_dev *retu_pm_power_off; +static struct resource tahvo_usb_res[] = { + { + .name = "tahvo-usb", + .start = TAHVO_INT_VBUS, + .end = TAHVO_INT_VBUS, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell tahvo_devs[] = { + { + .name = "tahvo-usb", + .resources = tahvo_usb_res, + .num_resources = ARRAY_SIZE(tahvo_usb_res), + }, +}; + +static struct regmap_irq tahvo_irqs[] = { + [TAHVO_INT_VBUS] = { + .mask = 1 << TAHVO_INT_VBUS, + } +}; + +static struct regmap_irq_chip tahvo_irq_chip = { + .name = "TAHVO", + .irqs = tahvo_irqs, + .num_irqs = ARRAY_SIZE(tahvo_irqs), + .num_regs = 1, + .status_base = RETU_REG_IDR, + .mask_base = TAHVO_REG_IMR, + .ack_base = RETU_REG_IDR, +}; + +static const struct retu_data { + char *chip_name; + char *companion_name; + struct regmap_irq_chip *irq_chip; + struct mfd_cell *children; + int nchildren; +} retu_data[] = { + [0] = { + .chip_name = "Retu", + .companion_name = "Vilma", + .irq_chip = &retu_irq_chip, + .children = retu_devs, + .nchildren = ARRAY_SIZE(retu_devs), + }, + [1] = { + .chip_name = "Tahvo", + .companion_name = "Betty", + .irq_chip = &tahvo_irq_chip, + .children = tahvo_devs, + .nchildren = ARRAY_SIZE(tahvo_devs), + } +}; + int retu_read(struct retu_dev *rdev, u8 reg) { int ret; @@ -173,9 +230,14 @@ static struct regmap_config retu_config = { static int retu_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { + struct retu_data const *rdat; struct retu_dev *rdev; int ret; + if (i2c->addr > ARRAY_SIZE(retu_data)) + return -ENODEV; + rdat = &retu_data[i2c->addr - 1]; + rdev = devm_kzalloc(&i2c->dev, sizeof(*rdev), GFP_KERNEL); if (rdev == NULL) return -ENOMEM; @@ -190,25 +252,27 @@ static int retu_probe(struct i2c_client *i2c, const struct i2c_device_id *id) ret = retu_read(rdev, RETU_REG_ASICR); if (ret < 0) { - dev_err(rdev->dev, "could not read Retu revision: %d\n", ret); + dev_err(rdev->dev, "could not read %s revision: %d\n", + rdat->chip_name, ret); return ret; } - dev_info(rdev->dev, "Retu%s v%d.%d found\n", - (ret & RETU_REG_ASICR_VILMA) ? " & Vilma" : "", + dev_info(rdev->dev, "%s%s%s v%d.%d found\n", rdat->chip_name, + (ret & RETU_REG_ASICR_VILMA) ? " & " : "", + (ret & RETU_REG_ASICR_VILMA) ? rdat->companion_name : "", (ret >> 4) & 0x7, ret & 0xf); - /* Mask all RETU interrupts. */ - ret = retu_write(rdev, RETU_REG_IMR, 0xffff); + /* Mask all interrupts. */ + ret = retu_write(rdev, rdat->irq_chip->mask_base, 0xffff); if (ret < 0) return ret; ret = regmap_add_irq_chip(rdev->regmap, i2c->irq, IRQF_ONESHOT, -1, - &retu_irq_chip, &rdev->irq_data); + rdat->irq_chip, &rdev->irq_data); if (ret < 0) return ret; - ret = mfd_add_devices(rdev->dev, -1, retu_devs, ARRAY_SIZE(retu_devs), + ret = mfd_add_devices(rdev->dev, -1, rdat->children, rdat->nchildren, NULL, regmap_irq_chip_get_base(rdev->irq_data), NULL); if (ret < 0) { @@ -216,7 +280,7 @@ static int retu_probe(struct i2c_client *i2c, const struct i2c_device_id *id) return ret; } - if (!pm_power_off) { + if (i2c->addr == 1 && !pm_power_off) { retu_pm_power_off = rdev; pm_power_off = retu_power_off; } @@ -240,6 +304,7 @@ static int retu_remove(struct i2c_client *i2c) static const struct i2c_device_id retu_id[] = { { "retu-mfd", 0 }, + { "tahvo-mfd", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, retu_id); diff --git a/drivers/mfd/rts5249.c b/drivers/mfd/rts5249.c new file mode 100644 index 000000000000..15dc848bc081 --- /dev/null +++ b/drivers/mfd/rts5249.c @@ -0,0 +1,241 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009-2013 Realtek Semiconductor Corp. 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, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + * No. 128, West Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/mfd/rtsx_pci.h> + +#include "rtsx_pcr.h" + +static u8 rts5249_get_ic_version(struct rtsx_pcr *pcr) +{ + u8 val; + + rtsx_pci_read_register(pcr, DUMMY_REG_RESET_0, &val); + return val & 0x0F; +} + +static int rts5249_extra_init_hw(struct rtsx_pcr *pcr) +{ + rtsx_pci_init_cmd(pcr); + + /* Configure GPIO as output */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, GPIO_CTL, 0x02, 0x02); + /* Switch LDO3318 source from DV33 to card_3v3 */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LDO_PWR_SEL, 0x03, 0x00); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LDO_PWR_SEL, 0x03, 0x01); + /* LED shine disabled, set initial shine cycle period */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, OLT_LED_CTL, 0x0F, 0x02); + /* Correct driving */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + SD30_CLK_DRIVE_SEL, 0xFF, 0x99); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + SD30_CMD_DRIVE_SEL, 0xFF, 0x99); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + SD30_DAT_DRIVE_SEL, 0xFF, 0x92); + + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rts5249_optimize_phy(struct rtsx_pcr *pcr) +{ + int err; + + err = rtsx_pci_write_phy_register(pcr, PHY_REG_REV, 0xFE46); + if (err < 0) + return err; + + msleep(1); + + return rtsx_pci_write_phy_register(pcr, PHY_BPCR, 0x05C0); +} + +static int rts5249_turn_on_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, GPIO_CTL, 0x02, 0x02); +} + +static int rts5249_turn_off_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, GPIO_CTL, 0x02, 0x00); +} + +static int rts5249_enable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, OLT_LED_CTL, 0x08, 0x08); +} + +static int rts5249_disable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, OLT_LED_CTL, 0x08, 0x00); +} + +static int rts5249_card_power_on(struct rtsx_pcr *pcr, int card) +{ + int err; + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_VCC_PARTIAL_POWER_ON); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x02); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + msleep(5); + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_VCC_POWER_ON); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x06); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + return 0; +} + +static int rts5249_card_power_off(struct rtsx_pcr *pcr, int card) +{ + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_POWER_OFF); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x00); + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rts5249_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage) +{ + int err; + u8 clk_drive, cmd_drive, dat_drive; + + if (voltage == OUTPUT_3V3) { + err = rtsx_pci_write_phy_register(pcr, PHY_TUNE, 0x4FC0 | 0x24); + if (err < 0) + return err; + clk_drive = 0x99; + cmd_drive = 0x99; + dat_drive = 0x92; + } else if (voltage == OUTPUT_1V8) { + err = rtsx_pci_write_phy_register(pcr, PHY_BACR, 0x3C02); + if (err < 0) + return err; + err = rtsx_pci_write_phy_register(pcr, PHY_TUNE, 0x4C40 | 0x24); + if (err < 0) + return err; + clk_drive = 0xb3; + cmd_drive = 0xb3; + dat_drive = 0xb3; + } else { + return -EINVAL; + } + + /* set pad drive */ + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_CLK_DRIVE_SEL, + 0xFF, clk_drive); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_CMD_DRIVE_SEL, + 0xFF, cmd_drive); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_DAT_DRIVE_SEL, + 0xFF, dat_drive); + return rtsx_pci_send_cmd(pcr, 100); +} + +static const struct pcr_ops rts5249_pcr_ops = { + .extra_init_hw = rts5249_extra_init_hw, + .optimize_phy = rts5249_optimize_phy, + .turn_on_led = rts5249_turn_on_led, + .turn_off_led = rts5249_turn_off_led, + .enable_auto_blink = rts5249_enable_auto_blink, + .disable_auto_blink = rts5249_disable_auto_blink, + .card_power_on = rts5249_card_power_on, + .card_power_off = rts5249_card_power_off, + .switch_output_voltage = rts5249_switch_output_voltage, +}; + +/* SD Pull Control Enable: + * SD_DAT[3:0] ==> pull up + * SD_CD ==> pull up + * SD_WP ==> pull up + * SD_CMD ==> pull up + * SD_CLK ==> pull down + */ +static const u32 rts5249_sd_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x66), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xE9), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0xAA), + 0, +}; + +/* SD Pull Control Disable: + * SD_DAT[3:0] ==> pull down + * SD_CD ==> pull up + * SD_WP ==> pull down + * SD_CMD ==> pull down + * SD_CLK ==> pull down + */ +static const u32 rts5249_sd_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x66), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xD5), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x55), + 0, +}; + +/* MS Pull Control Enable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5249_ms_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +/* MS Pull Control Disable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5249_ms_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +void rts5249_init_params(struct rtsx_pcr *pcr) +{ + pcr->extra_caps = EXTRA_CAPS_SD_SDR50 | EXTRA_CAPS_SD_SDR104; + pcr->num_slots = 2; + pcr->ops = &rts5249_pcr_ops; + + pcr->ic_version = rts5249_get_ic_version(pcr); + pcr->sd_pull_ctl_enable_tbl = rts5249_sd_pull_ctl_enable_tbl; + pcr->sd_pull_ctl_disable_tbl = rts5249_sd_pull_ctl_disable_tbl; + pcr->ms_pull_ctl_enable_tbl = rts5249_ms_pull_ctl_enable_tbl; + pcr->ms_pull_ctl_disable_tbl = rts5249_ms_pull_ctl_disable_tbl; +} diff --git a/drivers/mfd/rtsx_pcr.c b/drivers/mfd/rtsx_pcr.c index 2f12cc13489a..e968c01ca2ac 100644 --- a/drivers/mfd/rtsx_pcr.c +++ b/drivers/mfd/rtsx_pcr.c @@ -56,6 +56,7 @@ static DEFINE_PCI_DEVICE_TABLE(rtsx_pci_ids) = { { PCI_DEVICE(0x10EC, 0x5229), PCI_CLASS_OTHERS << 16, 0xFF0000 }, { PCI_DEVICE(0x10EC, 0x5289), PCI_CLASS_OTHERS << 16, 0xFF0000 }, { PCI_DEVICE(0x10EC, 0x5227), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { PCI_DEVICE(0x10EC, 0x5249), PCI_CLASS_OTHERS << 16, 0xFF0000 }, { 0, } }; @@ -1033,6 +1034,10 @@ static int rtsx_pci_init_chip(struct rtsx_pcr *pcr) case 0x5227: rts5227_init_params(pcr); break; + + case 0x5249: + rts5249_init_params(pcr); + break; } dev_dbg(&(pcr->pci->dev), "PID: 0x%04x, IC version: 0x%02x\n", @@ -1138,7 +1143,7 @@ static int rtsx_pci_probe(struct pci_dev *pcidev, ret = rtsx_pci_acquire_irq(pcr); if (ret < 0) - goto free_dma; + goto disable_msi; pci_set_master(pcidev); synchronize_irq(pcr->irq); @@ -1162,7 +1167,9 @@ static int rtsx_pci_probe(struct pci_dev *pcidev, disable_irq: free_irq(pcr->irq, (void *)pcr); -free_dma: +disable_msi: + if (pcr->msi_en) + pci_disable_msi(pcr->pci); dma_free_coherent(&(pcr->pci->dev), RTSX_RESV_BUF_LEN, pcr->rtsx_resv_buf, pcr->rtsx_resv_buf_addr); unmap: diff --git a/drivers/mfd/rtsx_pcr.h b/drivers/mfd/rtsx_pcr.h index 2b3ab8a04823..55fcfc25c4e4 100644 --- a/drivers/mfd/rtsx_pcr.h +++ b/drivers/mfd/rtsx_pcr.h @@ -32,5 +32,6 @@ void rts5209_init_params(struct rtsx_pcr *pcr); void rts5229_init_params(struct rtsx_pcr *pcr); void rtl8411_init_params(struct rtsx_pcr *pcr); void rts5227_init_params(struct rtsx_pcr *pcr); +void rts5249_init_params(struct rtsx_pcr *pcr); #endif diff --git a/drivers/mfd/si476x-cmd.c b/drivers/mfd/si476x-cmd.c new file mode 100644 index 000000000000..de48b4e88450 --- /dev/null +++ b/drivers/mfd/si476x-cmd.c @@ -0,0 +1,1553 @@ +/* + * drivers/mfd/si476x-cmd.c -- Subroutines implementing command + * protocol of si476x series of chips + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov <andrew.smirnov@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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/atomic.h> +#include <linux/i2c.h> +#include <linux/device.h> +#include <linux/gpio.h> +#include <linux/videodev2.h> + +#include <linux/mfd/si476x-core.h> + +#define msb(x) ((u8)((u16) x >> 8)) +#define lsb(x) ((u8)((u16) x & 0x00FF)) + + + +#define CMD_POWER_UP 0x01 +#define CMD_POWER_UP_A10_NRESP 1 +#define CMD_POWER_UP_A10_NARGS 5 + +#define CMD_POWER_UP_A20_NRESP 1 +#define CMD_POWER_UP_A20_NARGS 5 + +#define POWER_UP_DELAY_MS 110 + +#define CMD_POWER_DOWN 0x11 +#define CMD_POWER_DOWN_A10_NRESP 1 + +#define CMD_POWER_DOWN_A20_NRESP 1 +#define CMD_POWER_DOWN_A20_NARGS 1 + +#define CMD_FUNC_INFO 0x12 +#define CMD_FUNC_INFO_NRESP 7 + +#define CMD_SET_PROPERTY 0x13 +#define CMD_SET_PROPERTY_NARGS 5 +#define CMD_SET_PROPERTY_NRESP 1 + +#define CMD_GET_PROPERTY 0x14 +#define CMD_GET_PROPERTY_NARGS 3 +#define CMD_GET_PROPERTY_NRESP 4 + +#define CMD_AGC_STATUS 0x17 +#define CMD_AGC_STATUS_NRESP_A10 2 +#define CMD_AGC_STATUS_NRESP_A20 6 + +#define PIN_CFG_BYTE(x) (0x7F & (x)) +#define CMD_DIG_AUDIO_PIN_CFG 0x18 +#define CMD_DIG_AUDIO_PIN_CFG_NARGS 4 +#define CMD_DIG_AUDIO_PIN_CFG_NRESP 5 + +#define CMD_ZIF_PIN_CFG 0x19 +#define CMD_ZIF_PIN_CFG_NARGS 4 +#define CMD_ZIF_PIN_CFG_NRESP 5 + +#define CMD_IC_LINK_GPO_CTL_PIN_CFG 0x1A +#define CMD_IC_LINK_GPO_CTL_PIN_CFG_NARGS 4 +#define CMD_IC_LINK_GPO_CTL_PIN_CFG_NRESP 5 + +#define CMD_ANA_AUDIO_PIN_CFG 0x1B +#define CMD_ANA_AUDIO_PIN_CFG_NARGS 1 +#define CMD_ANA_AUDIO_PIN_CFG_NRESP 2 + +#define CMD_INTB_PIN_CFG 0x1C +#define CMD_INTB_PIN_CFG_NARGS 2 +#define CMD_INTB_PIN_CFG_A10_NRESP 6 +#define CMD_INTB_PIN_CFG_A20_NRESP 3 + +#define CMD_FM_TUNE_FREQ 0x30 +#define CMD_FM_TUNE_FREQ_A10_NARGS 5 +#define CMD_FM_TUNE_FREQ_A20_NARGS 3 +#define CMD_FM_TUNE_FREQ_NRESP 1 + +#define CMD_FM_RSQ_STATUS 0x32 + +#define CMD_FM_RSQ_STATUS_A10_NARGS 1 +#define CMD_FM_RSQ_STATUS_A10_NRESP 17 +#define CMD_FM_RSQ_STATUS_A30_NARGS 1 +#define CMD_FM_RSQ_STATUS_A30_NRESP 23 + + +#define CMD_FM_SEEK_START 0x31 +#define CMD_FM_SEEK_START_NARGS 1 +#define CMD_FM_SEEK_START_NRESP 1 + +#define CMD_FM_RDS_STATUS 0x36 +#define CMD_FM_RDS_STATUS_NARGS 1 +#define CMD_FM_RDS_STATUS_NRESP 16 + +#define CMD_FM_RDS_BLOCKCOUNT 0x37 +#define CMD_FM_RDS_BLOCKCOUNT_NARGS 1 +#define CMD_FM_RDS_BLOCKCOUNT_NRESP 8 + +#define CMD_FM_PHASE_DIVERSITY 0x38 +#define CMD_FM_PHASE_DIVERSITY_NARGS 1 +#define CMD_FM_PHASE_DIVERSITY_NRESP 1 + +#define CMD_FM_PHASE_DIV_STATUS 0x39 +#define CMD_FM_PHASE_DIV_STATUS_NRESP 2 + +#define CMD_AM_TUNE_FREQ 0x40 +#define CMD_AM_TUNE_FREQ_NARGS 3 +#define CMD_AM_TUNE_FREQ_NRESP 1 + +#define CMD_AM_RSQ_STATUS 0x42 +#define CMD_AM_RSQ_STATUS_NARGS 1 +#define CMD_AM_RSQ_STATUS_NRESP 13 + +#define CMD_AM_SEEK_START 0x41 +#define CMD_AM_SEEK_START_NARGS 1 +#define CMD_AM_SEEK_START_NRESP 1 + + +#define CMD_AM_ACF_STATUS 0x45 +#define CMD_AM_ACF_STATUS_NRESP 6 +#define CMD_AM_ACF_STATUS_NARGS 1 + +#define CMD_FM_ACF_STATUS 0x35 +#define CMD_FM_ACF_STATUS_NRESP 8 +#define CMD_FM_ACF_STATUS_NARGS 1 + +#define CMD_MAX_ARGS_COUNT (10) + + +enum si476x_acf_status_report_bits { + SI476X_ACF_BLEND_INT = (1 << 4), + SI476X_ACF_HIBLEND_INT = (1 << 3), + SI476X_ACF_HICUT_INT = (1 << 2), + SI476X_ACF_CHBW_INT = (1 << 1), + SI476X_ACF_SOFTMUTE_INT = (1 << 0), + + SI476X_ACF_SMUTE = (1 << 0), + SI476X_ACF_SMATTN = 0b11111, + SI476X_ACF_PILOT = (1 << 7), + SI476X_ACF_STBLEND = ~SI476X_ACF_PILOT, +}; + +enum si476x_agc_status_report_bits { + SI476X_AGC_MXHI = (1 << 5), + SI476X_AGC_MXLO = (1 << 4), + SI476X_AGC_LNAHI = (1 << 3), + SI476X_AGC_LNALO = (1 << 2), +}; + +enum si476x_errors { + SI476X_ERR_BAD_COMMAND = 0x10, + SI476X_ERR_BAD_ARG1 = 0x11, + SI476X_ERR_BAD_ARG2 = 0x12, + SI476X_ERR_BAD_ARG3 = 0x13, + SI476X_ERR_BAD_ARG4 = 0x14, + SI476X_ERR_BUSY = 0x18, + SI476X_ERR_BAD_INTERNAL_MEMORY = 0x20, + SI476X_ERR_BAD_PATCH = 0x30, + SI476X_ERR_BAD_BOOT_MODE = 0x31, + SI476X_ERR_BAD_PROPERTY = 0x40, +}; + +static int si476x_core_parse_and_nag_about_error(struct si476x_core *core) +{ + int err; + char *cause; + u8 buffer[2]; + + if (core->revision != SI476X_REVISION_A10) { + err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV, + buffer, sizeof(buffer)); + if (err == sizeof(buffer)) { + switch (buffer[1]) { + case SI476X_ERR_BAD_COMMAND: + cause = "Bad command"; + err = -EINVAL; + break; + case SI476X_ERR_BAD_ARG1: + cause = "Bad argument #1"; + err = -EINVAL; + break; + case SI476X_ERR_BAD_ARG2: + cause = "Bad argument #2"; + err = -EINVAL; + break; + case SI476X_ERR_BAD_ARG3: + cause = "Bad argument #3"; + err = -EINVAL; + break; + case SI476X_ERR_BAD_ARG4: + cause = "Bad argument #4"; + err = -EINVAL; + break; + case SI476X_ERR_BUSY: + cause = "Chip is busy"; + err = -EBUSY; + break; + case SI476X_ERR_BAD_INTERNAL_MEMORY: + cause = "Bad internal memory"; + err = -EIO; + break; + case SI476X_ERR_BAD_PATCH: + cause = "Bad patch"; + err = -EINVAL; + break; + case SI476X_ERR_BAD_BOOT_MODE: + cause = "Bad boot mode"; + err = -EINVAL; + break; + case SI476X_ERR_BAD_PROPERTY: + cause = "Bad property"; + err = -EINVAL; + break; + default: + cause = "Unknown"; + err = -EIO; + } + + dev_err(&core->client->dev, + "[Chip error status]: %s\n", cause); + } else { + dev_err(&core->client->dev, + "Failed to fetch error code\n"); + err = (err >= 0) ? -EIO : err; + } + } else { + err = -EIO; + } + + return err; +} + +/** + * si476x_core_send_command() - sends a command to si476x and waits its + * response + * @core: si476x_device structure for the device we are + * communicating with + * @command: command id + * @args: command arguments we are sending + * @argn: actual size of @args + * @response: buffer to place the expected response from the device + * @respn: actual size of @response + * @usecs: amount of time to wait before reading the response (in + * usecs) + * + * Function returns 0 on succsess and negative error code on + * failure + */ +static int si476x_core_send_command(struct si476x_core *core, + const u8 command, + const u8 args[], + const int argn, + u8 resp[], + const int respn, + const int usecs) +{ + struct i2c_client *client = core->client; + int err; + u8 data[CMD_MAX_ARGS_COUNT + 1]; + + if (argn > CMD_MAX_ARGS_COUNT) { + err = -ENOMEM; + goto exit; + } + + if (!client->adapter) { + err = -ENODEV; + goto exit; + } + + /* First send the command and its arguments */ + data[0] = command; + memcpy(&data[1], args, argn); + dev_dbg(&client->dev, "Command:\n %*ph\n", argn + 1, data); + + err = si476x_core_i2c_xfer(core, SI476X_I2C_SEND, + (char *) data, argn + 1); + if (err != argn + 1) { + dev_err(&core->client->dev, + "Error while sending command 0x%02x\n", + command); + err = (err >= 0) ? -EIO : err; + goto exit; + } + /* Set CTS to zero only after the command is send to avoid + * possible racing conditions when working in polling mode */ + atomic_set(&core->cts, 0); + + /* if (unlikely(command == CMD_POWER_DOWN) */ + if (!wait_event_timeout(core->command, + atomic_read(&core->cts), + usecs_to_jiffies(usecs) + 1)) + dev_warn(&core->client->dev, + "(%s) [CMD 0x%02x] Answer timeout.\n", + __func__, command); + + /* + When working in polling mode, for some reason the tuner will + report CTS bit as being set in the first status byte read, + but all the consequtive ones will return zeros until the + tuner is actually completed the POWER_UP command. To + workaround that we wait for second CTS to be reported + */ + if (unlikely(!core->client->irq && command == CMD_POWER_UP)) { + if (!wait_event_timeout(core->command, + atomic_read(&core->cts), + usecs_to_jiffies(usecs) + 1)) + dev_warn(&core->client->dev, + "(%s) Power up took too much time.\n", + __func__); + } + + /* Then get the response */ + err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV, resp, respn); + if (err != respn) { + dev_err(&core->client->dev, + "Error while reading response for command 0x%02x\n", + command); + err = (err >= 0) ? -EIO : err; + goto exit; + } + dev_dbg(&client->dev, "Response:\n %*ph\n", respn, resp); + + err = 0; + + if (resp[0] & SI476X_ERR) { + dev_err(&core->client->dev, + "[CMD 0x%02x] Chip set error flag\n", command); + err = si476x_core_parse_and_nag_about_error(core); + goto exit; + } + + if (!(resp[0] & SI476X_CTS)) + err = -EBUSY; +exit: + return err; +} + +static int si476x_cmd_clear_stc(struct si476x_core *core) +{ + int err; + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = true, + }; + + switch (core->power_up_parameters.func) { + case SI476X_FUNC_FM_RECEIVER: + err = si476x_core_cmd_fm_rsq_status(core, &args, NULL); + break; + case SI476X_FUNC_AM_RECEIVER: + err = si476x_core_cmd_am_rsq_status(core, &args, NULL); + break; + default: + err = -EINVAL; + } + + return err; +} + +static int si476x_cmd_tune_seek_freq(struct si476x_core *core, + uint8_t cmd, + const uint8_t args[], size_t argn, + uint8_t *resp, size_t respn) +{ + int err; + + + atomic_set(&core->stc, 0); + err = si476x_core_send_command(core, cmd, args, argn, resp, respn, + SI476X_TIMEOUT_TUNE); + if (!err) { + wait_event_killable(core->tuning, + atomic_read(&core->stc)); + si476x_cmd_clear_stc(core); + } + + return err; +} + +/** + * si476x_cmd_func_info() - send 'FUNC_INFO' command to the device + * @core: device to send the command to + * @info: struct si476x_func_info to fill all the information + * returned by the command + * + * The command requests the firmware and patch version for currently + * loaded firmware (dependent on the function of the device FM/AM/WB) + * + * Function returns 0 on succsess and negative error code on + * failure + */ +int si476x_core_cmd_func_info(struct si476x_core *core, + struct si476x_func_info *info) +{ + int err; + u8 resp[CMD_FUNC_INFO_NRESP]; + + err = si476x_core_send_command(core, CMD_FUNC_INFO, + NULL, 0, + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + + info->firmware.major = resp[1]; + info->firmware.minor[0] = resp[2]; + info->firmware.minor[1] = resp[3]; + + info->patch_id = ((u16) resp[4] << 8) | resp[5]; + info->func = resp[6]; + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_func_info); + +/** + * si476x_cmd_set_property() - send 'SET_PROPERTY' command to the device + * @core: device to send the command to + * @property: property address + * @value: property value + * + * Function returns 0 on succsess and negative error code on + * failure + */ +int si476x_core_cmd_set_property(struct si476x_core *core, + u16 property, u16 value) +{ + u8 resp[CMD_SET_PROPERTY_NRESP]; + const u8 args[CMD_SET_PROPERTY_NARGS] = { + 0x00, + msb(property), + lsb(property), + msb(value), + lsb(value), + }; + + return si476x_core_send_command(core, CMD_SET_PROPERTY, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_set_property); + +/** + * si476x_cmd_get_property() - send 'GET_PROPERTY' command to the device + * @core: device to send the command to + * @property: property address + * + * Function return the value of property as u16 on success or a + * negative error on failure + */ +int si476x_core_cmd_get_property(struct si476x_core *core, u16 property) +{ + int err; + u8 resp[CMD_GET_PROPERTY_NRESP]; + const u8 args[CMD_GET_PROPERTY_NARGS] = { + 0x00, + msb(property), + lsb(property), + }; + + err = si476x_core_send_command(core, CMD_GET_PROPERTY, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + if (err < 0) + return err; + else + return be16_to_cpup((__be16 *)(resp + 2)); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_get_property); + +/** + * si476x_cmd_dig_audio_pin_cfg() - send 'DIG_AUDIO_PIN_CFG' command to + * the device + * @core: device to send the command to + * @dclk: DCLK pin function configuration: + * #SI476X_DCLK_NOOP - do not modify the behaviour + * #SI476X_DCLK_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * #SI476X_DCLK_DAUDIO - set the pin to be a part of digital + * audio interface + * @dfs: DFS pin function configuration: + * #SI476X_DFS_NOOP - do not modify the behaviour + * #SI476X_DFS_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_DFS_DAUDIO - set the pin to be a part of digital + * audio interface + * @dout - DOUT pin function configuration: + * SI476X_DOUT_NOOP - do not modify the behaviour + * SI476X_DOUT_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_DOUT_I2S_OUTPUT - set this pin to be digital out on I2S + * port 1 + * SI476X_DOUT_I2S_INPUT - set this pin to be digital in on I2S + * port 1 + * @xout - XOUT pin function configuration: + * SI476X_XOUT_NOOP - do not modify the behaviour + * SI476X_XOUT_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_XOUT_I2S_INPUT - set this pin to be digital in on I2S + * port 1 + * SI476X_XOUT_MODE_SELECT - set this pin to be the input that + * selects the mode of the I2S audio + * combiner (analog or HD) + * [SI4761/63/65/67 Only] + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_dig_audio_pin_cfg(struct si476x_core *core, + enum si476x_dclk_config dclk, + enum si476x_dfs_config dfs, + enum si476x_dout_config dout, + enum si476x_xout_config xout) +{ + u8 resp[CMD_DIG_AUDIO_PIN_CFG_NRESP]; + const u8 args[CMD_DIG_AUDIO_PIN_CFG_NARGS] = { + PIN_CFG_BYTE(dclk), + PIN_CFG_BYTE(dfs), + PIN_CFG_BYTE(dout), + PIN_CFG_BYTE(xout), + }; + + return si476x_core_send_command(core, CMD_DIG_AUDIO_PIN_CFG, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_dig_audio_pin_cfg); + +/** + * si476x_cmd_zif_pin_cfg - send 'ZIF_PIN_CFG_COMMAND' + * @core - device to send the command to + * @iqclk - IQCL pin function configuration: + * SI476X_IQCLK_NOOP - do not modify the behaviour + * SI476X_IQCLK_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_IQCLK_IQ - set pin to be a part of I/Q interace + * in master mode + * @iqfs - IQFS pin function configuration: + * SI476X_IQFS_NOOP - do not modify the behaviour + * SI476X_IQFS_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_IQFS_IQ - set pin to be a part of I/Q interace + * in master mode + * @iout - IOUT pin function configuration: + * SI476X_IOUT_NOOP - do not modify the behaviour + * SI476X_IOUT_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_IOUT_OUTPUT - set pin to be I out + * @qout - QOUT pin function configuration: + * SI476X_QOUT_NOOP - do not modify the behaviour + * SI476X_QOUT_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_QOUT_OUTPUT - set pin to be Q out + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_zif_pin_cfg(struct si476x_core *core, + enum si476x_iqclk_config iqclk, + enum si476x_iqfs_config iqfs, + enum si476x_iout_config iout, + enum si476x_qout_config qout) +{ + u8 resp[CMD_ZIF_PIN_CFG_NRESP]; + const u8 args[CMD_ZIF_PIN_CFG_NARGS] = { + PIN_CFG_BYTE(iqclk), + PIN_CFG_BYTE(iqfs), + PIN_CFG_BYTE(iout), + PIN_CFG_BYTE(qout), + }; + + return si476x_core_send_command(core, CMD_ZIF_PIN_CFG, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_zif_pin_cfg); + +/** + * si476x_cmd_ic_link_gpo_ctl_pin_cfg - send + * 'IC_LINK_GPIO_CTL_PIN_CFG' comand to the device + * @core - device to send the command to + * @icin - ICIN pin function configuration: + * SI476X_ICIN_NOOP - do not modify the behaviour + * SI476X_ICIN_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_ICIN_GPO1_HIGH - set pin to be an output, drive it high + * SI476X_ICIN_GPO1_LOW - set pin to be an output, drive it low + * SI476X_ICIN_IC_LINK - set the pin to be a part of Inter-Chip link + * @icip - ICIP pin function configuration: + * SI476X_ICIP_NOOP - do not modify the behaviour + * SI476X_ICIP_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_ICIP_GPO1_HIGH - set pin to be an output, drive it high + * SI476X_ICIP_GPO1_LOW - set pin to be an output, drive it low + * SI476X_ICIP_IC_LINK - set the pin to be a part of Inter-Chip link + * @icon - ICON pin function configuration: + * SI476X_ICON_NOOP - do not modify the behaviour + * SI476X_ICON_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_ICON_I2S - set the pin to be a part of audio + * interface in slave mode (DCLK) + * SI476X_ICON_IC_LINK - set the pin to be a part of Inter-Chip link + * @icop - ICOP pin function configuration: + * SI476X_ICOP_NOOP - do not modify the behaviour + * SI476X_ICOP_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_ICOP_I2S - set the pin to be a part of audio + * interface in slave mode (DOUT) + * [Si4761/63/65/67 Only] + * SI476X_ICOP_IC_LINK - set the pin to be a part of Inter-Chip link + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_ic_link_gpo_ctl_pin_cfg(struct si476x_core *core, + enum si476x_icin_config icin, + enum si476x_icip_config icip, + enum si476x_icon_config icon, + enum si476x_icop_config icop) +{ + u8 resp[CMD_IC_LINK_GPO_CTL_PIN_CFG_NRESP]; + const u8 args[CMD_IC_LINK_GPO_CTL_PIN_CFG_NARGS] = { + PIN_CFG_BYTE(icin), + PIN_CFG_BYTE(icip), + PIN_CFG_BYTE(icon), + PIN_CFG_BYTE(icop), + }; + + return si476x_core_send_command(core, CMD_IC_LINK_GPO_CTL_PIN_CFG, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_ic_link_gpo_ctl_pin_cfg); + +/** + * si476x_cmd_ana_audio_pin_cfg - send 'ANA_AUDIO_PIN_CFG' to the + * device + * @core - device to send the command to + * @lrout - LROUT pin function configuration: + * SI476X_LROUT_NOOP - do not modify the behaviour + * SI476X_LROUT_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_LROUT_AUDIO - set pin to be audio output + * SI476X_LROUT_MPX - set pin to be MPX output + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_ana_audio_pin_cfg(struct si476x_core *core, + enum si476x_lrout_config lrout) +{ + u8 resp[CMD_ANA_AUDIO_PIN_CFG_NRESP]; + const u8 args[CMD_ANA_AUDIO_PIN_CFG_NARGS] = { + PIN_CFG_BYTE(lrout), + }; + + return si476x_core_send_command(core, CMD_ANA_AUDIO_PIN_CFG, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_ana_audio_pin_cfg); + + +/** + * si476x_cmd_intb_pin_cfg - send 'INTB_PIN_CFG' command to the device + * @core - device to send the command to + * @intb - INTB pin function configuration: + * SI476X_INTB_NOOP - do not modify the behaviour + * SI476X_INTB_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_INTB_DAUDIO - set pin to be a part of digital + * audio interface in slave mode + * SI476X_INTB_IRQ - set pin to be an interrupt request line + * @a1 - A1 pin function configuration: + * SI476X_A1_NOOP - do not modify the behaviour + * SI476X_A1_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_A1_IRQ - set pin to be an interrupt request line + * + * Function returns 0 on success and negative error code on failure + */ +static int si476x_core_cmd_intb_pin_cfg_a10(struct si476x_core *core, + enum si476x_intb_config intb, + enum si476x_a1_config a1) +{ + u8 resp[CMD_INTB_PIN_CFG_A10_NRESP]; + const u8 args[CMD_INTB_PIN_CFG_NARGS] = { + PIN_CFG_BYTE(intb), + PIN_CFG_BYTE(a1), + }; + + return si476x_core_send_command(core, CMD_INTB_PIN_CFG, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} + +static int si476x_core_cmd_intb_pin_cfg_a20(struct si476x_core *core, + enum si476x_intb_config intb, + enum si476x_a1_config a1) +{ + u8 resp[CMD_INTB_PIN_CFG_A20_NRESP]; + const u8 args[CMD_INTB_PIN_CFG_NARGS] = { + PIN_CFG_BYTE(intb), + PIN_CFG_BYTE(a1), + }; + + return si476x_core_send_command(core, CMD_INTB_PIN_CFG, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} + + + +/** + * si476x_cmd_am_rsq_status - send 'AM_RSQ_STATUS' command to the + * device + * @core - device to send the command to + * @rsqack - if set command clears RSQINT, SNRINT, SNRLINT, RSSIHINT, + * RSSSILINT, BLENDINT, MULTHINT and MULTLINT + * @attune - when set the values in the status report are the values + * that were calculated at tune + * @cancel - abort ongoing seek/tune opertation + * @stcack - clear the STCINT bin in status register + * @report - all signal quality information retured by the command + * (if NULL then the output of the command is ignored) + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_am_rsq_status(struct si476x_core *core, + struct si476x_rsq_status_args *rsqargs, + struct si476x_rsq_status_report *report) +{ + int err; + u8 resp[CMD_AM_RSQ_STATUS_NRESP]; + const u8 args[CMD_AM_RSQ_STATUS_NARGS] = { + rsqargs->rsqack << 3 | rsqargs->attune << 2 | + rsqargs->cancel << 1 | rsqargs->stcack, + }; + + err = si476x_core_send_command(core, CMD_AM_RSQ_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + /* + * Besides getting received signal quality information this + * command can be used to just acknowledge different interrupt + * flags in those cases it is useless to copy and parse + * received data so user can pass NULL, and thus avoid + * unnecessary copying. + */ + if (!report) + return err; + + report->snrhint = 0b00001000 & resp[1]; + report->snrlint = 0b00000100 & resp[1]; + report->rssihint = 0b00000010 & resp[1]; + report->rssilint = 0b00000001 & resp[1]; + + report->bltf = 0b10000000 & resp[2]; + report->snr_ready = 0b00100000 & resp[2]; + report->rssiready = 0b00001000 & resp[2]; + report->afcrl = 0b00000010 & resp[2]; + report->valid = 0b00000001 & resp[2]; + + report->readfreq = be16_to_cpup((__be16 *)(resp + 3)); + report->freqoff = resp[5]; + report->rssi = resp[6]; + report->snr = resp[7]; + report->lassi = resp[9]; + report->hassi = resp[10]; + report->mult = resp[11]; + report->dev = resp[12]; + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_am_rsq_status); + +int si476x_core_cmd_fm_acf_status(struct si476x_core *core, + struct si476x_acf_status_report *report) +{ + int err; + u8 resp[CMD_FM_ACF_STATUS_NRESP]; + const u8 args[CMD_FM_ACF_STATUS_NARGS] = { + 0x0, + }; + + if (!report) + return -EINVAL; + + err = si476x_core_send_command(core, CMD_FM_ACF_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + if (err < 0) + return err; + + report->blend_int = resp[1] & SI476X_ACF_BLEND_INT; + report->hblend_int = resp[1] & SI476X_ACF_HIBLEND_INT; + report->hicut_int = resp[1] & SI476X_ACF_HICUT_INT; + report->chbw_int = resp[1] & SI476X_ACF_CHBW_INT; + report->softmute_int = resp[1] & SI476X_ACF_SOFTMUTE_INT; + report->smute = resp[2] & SI476X_ACF_SMUTE; + report->smattn = resp[3] & SI476X_ACF_SMATTN; + report->chbw = resp[4]; + report->hicut = resp[5]; + report->hiblend = resp[6]; + report->pilot = resp[7] & SI476X_ACF_PILOT; + report->stblend = resp[7] & SI476X_ACF_STBLEND; + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_acf_status); + +int si476x_core_cmd_am_acf_status(struct si476x_core *core, + struct si476x_acf_status_report *report) +{ + int err; + u8 resp[CMD_AM_ACF_STATUS_NRESP]; + const u8 args[CMD_AM_ACF_STATUS_NARGS] = { + 0x0, + }; + + if (!report) + return -EINVAL; + + err = si476x_core_send_command(core, CMD_AM_ACF_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + if (err < 0) + return err; + + report->blend_int = resp[1] & SI476X_ACF_BLEND_INT; + report->hblend_int = resp[1] & SI476X_ACF_HIBLEND_INT; + report->hicut_int = resp[1] & SI476X_ACF_HICUT_INT; + report->chbw_int = resp[1] & SI476X_ACF_CHBW_INT; + report->softmute_int = resp[1] & SI476X_ACF_SOFTMUTE_INT; + report->smute = resp[2] & SI476X_ACF_SMUTE; + report->smattn = resp[3] & SI476X_ACF_SMATTN; + report->chbw = resp[4]; + report->hicut = resp[5]; + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_am_acf_status); + + +/** + * si476x_cmd_fm_seek_start - send 'FM_SEEK_START' command to the + * device + * @core - device to send the command to + * @seekup - if set the direction of the search is 'up' + * @wrap - if set seek wraps when hitting band limit + * + * This function begins search for a valid station. The station is + * considered valid when 'FM_VALID_SNR_THRESHOLD' and + * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria + * are met. +} * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_fm_seek_start(struct si476x_core *core, + bool seekup, bool wrap) +{ + u8 resp[CMD_FM_SEEK_START_NRESP]; + const u8 args[CMD_FM_SEEK_START_NARGS] = { + seekup << 3 | wrap << 2, + }; + + return si476x_cmd_tune_seek_freq(core, CMD_FM_SEEK_START, + args, sizeof(args), + resp, sizeof(resp)); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_seek_start); + +/** + * si476x_cmd_fm_rds_status - send 'FM_RDS_STATUS' command to the + * device + * @core - device to send the command to + * @status_only - if set the data is not removed from RDSFIFO, + * RDSFIFOUSED is not decremented and data in all the + * rest RDS data contains the last valid info received + * @mtfifo if set the command clears RDS receive FIFO + * @intack if set the command clards the RDSINT bit. + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_fm_rds_status(struct si476x_core *core, + bool status_only, + bool mtfifo, + bool intack, + struct si476x_rds_status_report *report) +{ + int err; + u8 resp[CMD_FM_RDS_STATUS_NRESP]; + const u8 args[CMD_FM_RDS_STATUS_NARGS] = { + status_only << 2 | mtfifo << 1 | intack, + }; + + err = si476x_core_send_command(core, CMD_FM_RDS_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + /* + * Besides getting RDS status information this command can be + * used to just acknowledge different interrupt flags in those + * cases it is useless to copy and parse received data so user + * can pass NULL, and thus avoid unnecessary copying. + */ + if (err < 0 || report == NULL) + return err; + + report->rdstpptyint = 0b00010000 & resp[1]; + report->rdspiint = 0b00001000 & resp[1]; + report->rdssyncint = 0b00000010 & resp[1]; + report->rdsfifoint = 0b00000001 & resp[1]; + + report->tpptyvalid = 0b00010000 & resp[2]; + report->pivalid = 0b00001000 & resp[2]; + report->rdssync = 0b00000010 & resp[2]; + report->rdsfifolost = 0b00000001 & resp[2]; + + report->tp = 0b00100000 & resp[3]; + report->pty = 0b00011111 & resp[3]; + + report->pi = be16_to_cpup((__be16 *)(resp + 4)); + report->rdsfifoused = resp[6]; + + report->ble[V4L2_RDS_BLOCK_A] = 0b11000000 & resp[7]; + report->ble[V4L2_RDS_BLOCK_B] = 0b00110000 & resp[7]; + report->ble[V4L2_RDS_BLOCK_C] = 0b00001100 & resp[7]; + report->ble[V4L2_RDS_BLOCK_D] = 0b00000011 & resp[7]; + + report->rds[V4L2_RDS_BLOCK_A].block = V4L2_RDS_BLOCK_A; + report->rds[V4L2_RDS_BLOCK_A].msb = resp[8]; + report->rds[V4L2_RDS_BLOCK_A].lsb = resp[9]; + + report->rds[V4L2_RDS_BLOCK_B].block = V4L2_RDS_BLOCK_B; + report->rds[V4L2_RDS_BLOCK_B].msb = resp[10]; + report->rds[V4L2_RDS_BLOCK_B].lsb = resp[11]; + + report->rds[V4L2_RDS_BLOCK_C].block = V4L2_RDS_BLOCK_C; + report->rds[V4L2_RDS_BLOCK_C].msb = resp[12]; + report->rds[V4L2_RDS_BLOCK_C].lsb = resp[13]; + + report->rds[V4L2_RDS_BLOCK_D].block = V4L2_RDS_BLOCK_D; + report->rds[V4L2_RDS_BLOCK_D].msb = resp[14]; + report->rds[V4L2_RDS_BLOCK_D].lsb = resp[15]; + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rds_status); + +int si476x_core_cmd_fm_rds_blockcount(struct si476x_core *core, + bool clear, + struct si476x_rds_blockcount_report *report) +{ + int err; + u8 resp[CMD_FM_RDS_BLOCKCOUNT_NRESP]; + const u8 args[CMD_FM_RDS_BLOCKCOUNT_NARGS] = { + clear, + }; + + if (!report) + return -EINVAL; + + err = si476x_core_send_command(core, CMD_FM_RDS_BLOCKCOUNT, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + + if (!err) { + report->expected = be16_to_cpup((__be16 *)(resp + 2)); + report->received = be16_to_cpup((__be16 *)(resp + 4)); + report->uncorrectable = be16_to_cpup((__be16 *)(resp + 6)); + } + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rds_blockcount); + +int si476x_core_cmd_fm_phase_diversity(struct si476x_core *core, + enum si476x_phase_diversity_mode mode) +{ + u8 resp[CMD_FM_PHASE_DIVERSITY_NRESP]; + const u8 args[CMD_FM_PHASE_DIVERSITY_NARGS] = { + mode & 0b111, + }; + + return si476x_core_send_command(core, CMD_FM_PHASE_DIVERSITY, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_phase_diversity); +/** + * si476x_core_cmd_fm_phase_div_status() - get the phase diversity + * status + * + * @core: si476x device + * + * NOTE caller must hold core lock + * + * Function returns the value of the status bit in case of success and + * negative error code in case of failre. + */ +int si476x_core_cmd_fm_phase_div_status(struct si476x_core *core) +{ + int err; + u8 resp[CMD_FM_PHASE_DIV_STATUS_NRESP]; + + err = si476x_core_send_command(core, CMD_FM_PHASE_DIV_STATUS, + NULL, 0, + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + + return (err < 0) ? err : resp[1]; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_phase_div_status); + + +/** + * si476x_cmd_am_seek_start - send 'FM_SEEK_START' command to the + * device + * @core - device to send the command to + * @seekup - if set the direction of the search is 'up' + * @wrap - if set seek wraps when hitting band limit + * + * This function begins search for a valid station. The station is + * considered valid when 'FM_VALID_SNR_THRESHOLD' and + * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria + * are met. + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_am_seek_start(struct si476x_core *core, + bool seekup, bool wrap) +{ + u8 resp[CMD_AM_SEEK_START_NRESP]; + const u8 args[CMD_AM_SEEK_START_NARGS] = { + seekup << 3 | wrap << 2, + }; + + return si476x_cmd_tune_seek_freq(core, CMD_AM_SEEK_START, + args, sizeof(args), + resp, sizeof(resp)); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_am_seek_start); + + + +static int si476x_core_cmd_power_up_a10(struct si476x_core *core, + struct si476x_power_up_args *puargs) +{ + u8 resp[CMD_POWER_UP_A10_NRESP]; + const bool intsel = (core->pinmux.a1 == SI476X_A1_IRQ); + const bool ctsen = (core->client->irq != 0); + const u8 args[CMD_POWER_UP_A10_NARGS] = { + 0xF7, /* Reserved, always 0xF7 */ + 0x3F & puargs->xcload, /* First two bits are reserved to be + * zeros */ + ctsen << 7 | intsel << 6 | 0x07, /* Last five bits + * are reserved to + * be written as 0x7 */ + puargs->func << 4 | puargs->freq, + 0x11, /* Reserved, always 0x11 */ + }; + + return si476x_core_send_command(core, CMD_POWER_UP, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_TIMEOUT_POWER_UP); +} + +static int si476x_core_cmd_power_up_a20(struct si476x_core *core, + struct si476x_power_up_args *puargs) +{ + u8 resp[CMD_POWER_UP_A20_NRESP]; + const bool intsel = (core->pinmux.a1 == SI476X_A1_IRQ); + const bool ctsen = (core->client->irq != 0); + const u8 args[CMD_POWER_UP_A20_NARGS] = { + puargs->ibias6x << 7 | puargs->xstart, + 0x3F & puargs->xcload, /* First two bits are reserved to be + * zeros */ + ctsen << 7 | intsel << 6 | puargs->fastboot << 5 | + puargs->xbiashc << 3 | puargs->xbias, + puargs->func << 4 | puargs->freq, + 0x10 | puargs->xmode, + }; + + return si476x_core_send_command(core, CMD_POWER_UP, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_TIMEOUT_POWER_UP); +} + +static int si476x_core_cmd_power_down_a10(struct si476x_core *core, + struct si476x_power_down_args *pdargs) +{ + u8 resp[CMD_POWER_DOWN_A10_NRESP]; + + return si476x_core_send_command(core, CMD_POWER_DOWN, + NULL, 0, + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} + +static int si476x_core_cmd_power_down_a20(struct si476x_core *core, + struct si476x_power_down_args *pdargs) +{ + u8 resp[CMD_POWER_DOWN_A20_NRESP]; + const u8 args[CMD_POWER_DOWN_A20_NARGS] = { + pdargs->xosc, + }; + return si476x_core_send_command(core, CMD_POWER_DOWN, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} + +static int si476x_core_cmd_am_tune_freq_a10(struct si476x_core *core, + struct si476x_tune_freq_args *tuneargs) +{ + + const int am_freq = tuneargs->freq; + u8 resp[CMD_AM_TUNE_FREQ_NRESP]; + const u8 args[CMD_AM_TUNE_FREQ_NARGS] = { + (tuneargs->hd << 6), + msb(am_freq), + lsb(am_freq), + }; + + return si476x_cmd_tune_seek_freq(core, CMD_AM_TUNE_FREQ, args, + sizeof(args), + resp, sizeof(resp)); +} + +static int si476x_core_cmd_am_tune_freq_a20(struct si476x_core *core, + struct si476x_tune_freq_args *tuneargs) +{ + const int am_freq = tuneargs->freq; + u8 resp[CMD_AM_TUNE_FREQ_NRESP]; + const u8 args[CMD_AM_TUNE_FREQ_NARGS] = { + (tuneargs->zifsr << 6) | (tuneargs->injside & 0b11), + msb(am_freq), + lsb(am_freq), + }; + + return si476x_cmd_tune_seek_freq(core, CMD_AM_TUNE_FREQ, + args, sizeof(args), + resp, sizeof(resp)); +} + +static int si476x_core_cmd_fm_rsq_status_a10(struct si476x_core *core, + struct si476x_rsq_status_args *rsqargs, + struct si476x_rsq_status_report *report) +{ + int err; + u8 resp[CMD_FM_RSQ_STATUS_A10_NRESP]; + const u8 args[CMD_FM_RSQ_STATUS_A10_NARGS] = { + rsqargs->rsqack << 3 | rsqargs->attune << 2 | + rsqargs->cancel << 1 | rsqargs->stcack, + }; + + err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + /* + * Besides getting received signal quality information this + * command can be used to just acknowledge different interrupt + * flags in those cases it is useless to copy and parse + * received data so user can pass NULL, and thus avoid + * unnecessary copying. + */ + if (err < 0 || report == NULL) + return err; + + report->multhint = 0b10000000 & resp[1]; + report->multlint = 0b01000000 & resp[1]; + report->snrhint = 0b00001000 & resp[1]; + report->snrlint = 0b00000100 & resp[1]; + report->rssihint = 0b00000010 & resp[1]; + report->rssilint = 0b00000001 & resp[1]; + + report->bltf = 0b10000000 & resp[2]; + report->snr_ready = 0b00100000 & resp[2]; + report->rssiready = 0b00001000 & resp[2]; + report->afcrl = 0b00000010 & resp[2]; + report->valid = 0b00000001 & resp[2]; + + report->readfreq = be16_to_cpup((__be16 *)(resp + 3)); + report->freqoff = resp[5]; + report->rssi = resp[6]; + report->snr = resp[7]; + report->lassi = resp[9]; + report->hassi = resp[10]; + report->mult = resp[11]; + report->dev = resp[12]; + report->readantcap = be16_to_cpup((__be16 *)(resp + 13)); + report->assi = resp[15]; + report->usn = resp[16]; + + return err; +} + +static int si476x_core_cmd_fm_rsq_status_a20(struct si476x_core *core, + struct si476x_rsq_status_args *rsqargs, + struct si476x_rsq_status_report *report) +{ + int err; + u8 resp[CMD_FM_RSQ_STATUS_A10_NRESP]; + const u8 args[CMD_FM_RSQ_STATUS_A30_NARGS] = { + rsqargs->primary << 4 | rsqargs->rsqack << 3 | + rsqargs->attune << 2 | rsqargs->cancel << 1 | + rsqargs->stcack, + }; + + err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + /* + * Besides getting received signal quality information this + * command can be used to just acknowledge different interrupt + * flags in those cases it is useless to copy and parse + * received data so user can pass NULL, and thus avoid + * unnecessary copying. + */ + if (err < 0 || report == NULL) + return err; + + report->multhint = 0b10000000 & resp[1]; + report->multlint = 0b01000000 & resp[1]; + report->snrhint = 0b00001000 & resp[1]; + report->snrlint = 0b00000100 & resp[1]; + report->rssihint = 0b00000010 & resp[1]; + report->rssilint = 0b00000001 & resp[1]; + + report->bltf = 0b10000000 & resp[2]; + report->snr_ready = 0b00100000 & resp[2]; + report->rssiready = 0b00001000 & resp[2]; + report->afcrl = 0b00000010 & resp[2]; + report->valid = 0b00000001 & resp[2]; + + report->readfreq = be16_to_cpup((__be16 *)(resp + 3)); + report->freqoff = resp[5]; + report->rssi = resp[6]; + report->snr = resp[7]; + report->lassi = resp[9]; + report->hassi = resp[10]; + report->mult = resp[11]; + report->dev = resp[12]; + report->readantcap = be16_to_cpup((__be16 *)(resp + 13)); + report->assi = resp[15]; + report->usn = resp[16]; + + return err; +} + + +static int si476x_core_cmd_fm_rsq_status_a30(struct si476x_core *core, + struct si476x_rsq_status_args *rsqargs, + struct si476x_rsq_status_report *report) +{ + int err; + u8 resp[CMD_FM_RSQ_STATUS_A30_NRESP]; + const u8 args[CMD_FM_RSQ_STATUS_A30_NARGS] = { + rsqargs->primary << 4 | rsqargs->rsqack << 3 | + rsqargs->attune << 2 | rsqargs->cancel << 1 | + rsqargs->stcack, + }; + + err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + /* + * Besides getting received signal quality information this + * command can be used to just acknowledge different interrupt + * flags in those cases it is useless to copy and parse + * received data so user can pass NULL, and thus avoid + * unnecessary copying. + */ + if (err < 0 || report == NULL) + return err; + + report->multhint = 0b10000000 & resp[1]; + report->multlint = 0b01000000 & resp[1]; + report->snrhint = 0b00001000 & resp[1]; + report->snrlint = 0b00000100 & resp[1]; + report->rssihint = 0b00000010 & resp[1]; + report->rssilint = 0b00000001 & resp[1]; + + report->bltf = 0b10000000 & resp[2]; + report->snr_ready = 0b00100000 & resp[2]; + report->rssiready = 0b00001000 & resp[2]; + report->injside = 0b00000100 & resp[2]; + report->afcrl = 0b00000010 & resp[2]; + report->valid = 0b00000001 & resp[2]; + + report->readfreq = be16_to_cpup((__be16 *)(resp + 3)); + report->freqoff = resp[5]; + report->rssi = resp[6]; + report->snr = resp[7]; + report->issi = resp[8]; + report->lassi = resp[9]; + report->hassi = resp[10]; + report->mult = resp[11]; + report->dev = resp[12]; + report->readantcap = be16_to_cpup((__be16 *)(resp + 13)); + report->assi = resp[15]; + report->usn = resp[16]; + + report->pilotdev = resp[17]; + report->rdsdev = resp[18]; + report->assidev = resp[19]; + report->strongdev = resp[20]; + report->rdspi = be16_to_cpup((__be16 *)(resp + 21)); + + return err; +} + +static int si476x_core_cmd_fm_tune_freq_a10(struct si476x_core *core, + struct si476x_tune_freq_args *tuneargs) +{ + u8 resp[CMD_FM_TUNE_FREQ_NRESP]; + const u8 args[CMD_FM_TUNE_FREQ_A10_NARGS] = { + (tuneargs->hd << 6) | (tuneargs->tunemode << 4) + | (tuneargs->smoothmetrics << 2), + msb(tuneargs->freq), + lsb(tuneargs->freq), + msb(tuneargs->antcap), + lsb(tuneargs->antcap) + }; + + return si476x_cmd_tune_seek_freq(core, CMD_FM_TUNE_FREQ, + args, sizeof(args), + resp, sizeof(resp)); +} + +static int si476x_core_cmd_fm_tune_freq_a20(struct si476x_core *core, + struct si476x_tune_freq_args *tuneargs) +{ + u8 resp[CMD_FM_TUNE_FREQ_NRESP]; + const u8 args[CMD_FM_TUNE_FREQ_A20_NARGS] = { + (tuneargs->hd << 6) | (tuneargs->tunemode << 4) + | (tuneargs->smoothmetrics << 2) | (tuneargs->injside), + msb(tuneargs->freq), + lsb(tuneargs->freq), + }; + + return si476x_cmd_tune_seek_freq(core, CMD_FM_TUNE_FREQ, + args, sizeof(args), + resp, sizeof(resp)); +} + +static int si476x_core_cmd_agc_status_a20(struct si476x_core *core, + struct si476x_agc_status_report *report) +{ + int err; + u8 resp[CMD_AGC_STATUS_NRESP_A20]; + + if (!report) + return -EINVAL; + + err = si476x_core_send_command(core, CMD_AGC_STATUS, + NULL, 0, + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + if (err < 0) + return err; + + report->mxhi = resp[1] & SI476X_AGC_MXHI; + report->mxlo = resp[1] & SI476X_AGC_MXLO; + report->lnahi = resp[1] & SI476X_AGC_LNAHI; + report->lnalo = resp[1] & SI476X_AGC_LNALO; + report->fmagc1 = resp[2]; + report->fmagc2 = resp[3]; + report->pgagain = resp[4]; + report->fmwblang = resp[5]; + + return err; +} + +static int si476x_core_cmd_agc_status_a10(struct si476x_core *core, + struct si476x_agc_status_report *report) +{ + int err; + u8 resp[CMD_AGC_STATUS_NRESP_A10]; + + if (!report) + return -EINVAL; + + err = si476x_core_send_command(core, CMD_AGC_STATUS, + NULL, 0, + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + if (err < 0) + return err; + + report->mxhi = resp[1] & SI476X_AGC_MXHI; + report->mxlo = resp[1] & SI476X_AGC_MXLO; + report->lnahi = resp[1] & SI476X_AGC_LNAHI; + report->lnalo = resp[1] & SI476X_AGC_LNALO; + + return err; +} + +typedef int (*tune_freq_func_t) (struct si476x_core *core, + struct si476x_tune_freq_args *tuneargs); + +static struct { + int (*power_up) (struct si476x_core *, + struct si476x_power_up_args *); + int (*power_down) (struct si476x_core *, + struct si476x_power_down_args *); + + tune_freq_func_t fm_tune_freq; + tune_freq_func_t am_tune_freq; + + int (*fm_rsq_status)(struct si476x_core *, + struct si476x_rsq_status_args *, + struct si476x_rsq_status_report *); + + int (*agc_status)(struct si476x_core *, + struct si476x_agc_status_report *); + int (*intb_pin_cfg)(struct si476x_core *core, + enum si476x_intb_config intb, + enum si476x_a1_config a1); +} si476x_cmds_vtable[] = { + [SI476X_REVISION_A10] = { + .power_up = si476x_core_cmd_power_up_a10, + .power_down = si476x_core_cmd_power_down_a10, + .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a10, + .am_tune_freq = si476x_core_cmd_am_tune_freq_a10, + .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a10, + .agc_status = si476x_core_cmd_agc_status_a10, + .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a10, + }, + [SI476X_REVISION_A20] = { + .power_up = si476x_core_cmd_power_up_a20, + .power_down = si476x_core_cmd_power_down_a20, + .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a20, + .am_tune_freq = si476x_core_cmd_am_tune_freq_a20, + .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a20, + .agc_status = si476x_core_cmd_agc_status_a20, + .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a20, + }, + [SI476X_REVISION_A30] = { + .power_up = si476x_core_cmd_power_up_a20, + .power_down = si476x_core_cmd_power_down_a20, + .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a20, + .am_tune_freq = si476x_core_cmd_am_tune_freq_a20, + .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a30, + .agc_status = si476x_core_cmd_agc_status_a20, + .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a20, + }, +}; + +int si476x_core_cmd_power_up(struct si476x_core *core, + struct si476x_power_up_args *args) +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return si476x_cmds_vtable[core->revision].power_up(core, args); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_power_up); + +int si476x_core_cmd_power_down(struct si476x_core *core, + struct si476x_power_down_args *args) +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return si476x_cmds_vtable[core->revision].power_down(core, args); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_power_down); + +int si476x_core_cmd_fm_tune_freq(struct si476x_core *core, + struct si476x_tune_freq_args *args) +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return si476x_cmds_vtable[core->revision].fm_tune_freq(core, args); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_tune_freq); + +int si476x_core_cmd_am_tune_freq(struct si476x_core *core, + struct si476x_tune_freq_args *args) +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return si476x_cmds_vtable[core->revision].am_tune_freq(core, args); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_am_tune_freq); + +int si476x_core_cmd_fm_rsq_status(struct si476x_core *core, + struct si476x_rsq_status_args *args, + struct si476x_rsq_status_report *report) + +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return si476x_cmds_vtable[core->revision].fm_rsq_status(core, args, + report); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rsq_status); + +int si476x_core_cmd_agc_status(struct si476x_core *core, + struct si476x_agc_status_report *report) + +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return si476x_cmds_vtable[core->revision].agc_status(core, report); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_agc_status); + +int si476x_core_cmd_intb_pin_cfg(struct si476x_core *core, + enum si476x_intb_config intb, + enum si476x_a1_config a1) +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + + return si476x_cmds_vtable[core->revision].intb_pin_cfg(core, intb, a1); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_intb_pin_cfg); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); +MODULE_DESCRIPTION("API for command exchange for si476x"); diff --git a/drivers/mfd/si476x-i2c.c b/drivers/mfd/si476x-i2c.c new file mode 100644 index 000000000000..f5bc8e4bd4bf --- /dev/null +++ b/drivers/mfd/si476x-i2c.c @@ -0,0 +1,886 @@ +/* + * drivers/mfd/si476x-i2c.c -- Core device driver for si476x MFD + * device + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov <andrew.smirnov@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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ +#include <linux/module.h> + +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/i2c.h> +#include <linux/err.h> + +#include <linux/mfd/si476x-core.h> + +#define SI476X_MAX_IO_ERRORS 10 +#define SI476X_DRIVER_RDS_FIFO_DEPTH 128 + +/** + * si476x_core_config_pinmux() - pin function configuration function + * + * @core: Core device structure + * + * Configure the functions of the pins of the radio chip. + * + * The function returns zero in case of succes or negative error code + * otherwise. + */ +static int si476x_core_config_pinmux(struct si476x_core *core) +{ + int err; + dev_dbg(&core->client->dev, "Configuring pinmux\n"); + err = si476x_core_cmd_dig_audio_pin_cfg(core, + core->pinmux.dclk, + core->pinmux.dfs, + core->pinmux.dout, + core->pinmux.xout); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure digital audio pins(err = %d)\n", + err); + return err; + } + + err = si476x_core_cmd_zif_pin_cfg(core, + core->pinmux.iqclk, + core->pinmux.iqfs, + core->pinmux.iout, + core->pinmux.qout); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure ZIF pins(err = %d)\n", + err); + return err; + } + + err = si476x_core_cmd_ic_link_gpo_ctl_pin_cfg(core, + core->pinmux.icin, + core->pinmux.icip, + core->pinmux.icon, + core->pinmux.icop); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure IC-Link/GPO pins(err = %d)\n", + err); + return err; + } + + err = si476x_core_cmd_ana_audio_pin_cfg(core, + core->pinmux.lrout); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure analog audio pins(err = %d)\n", + err); + return err; + } + + err = si476x_core_cmd_intb_pin_cfg(core, + core->pinmux.intb, + core->pinmux.a1); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure interrupt pins(err = %d)\n", + err); + return err; + } + + return 0; +} + +static inline void si476x_core_schedule_polling_work(struct si476x_core *core) +{ + schedule_delayed_work(&core->status_monitor, + usecs_to_jiffies(SI476X_STATUS_POLL_US)); +} + +/** + * si476x_core_start() - early chip startup function + * @core: Core device structure + * @soft: When set, this flag forces "soft" startup, where "soft" + * power down is the one done by sending appropriate command instead + * of using reset pin of the tuner + * + * Perform required startup sequence to correctly power + * up the chip and perform initial configuration. It does the + * following sequence of actions: + * 1. Claims and enables the power supplies VD and VIO1 required + * for I2C interface of the chip operation. + * 2. Waits for 100us, pulls the reset line up, enables irq, + * waits for another 100us as it is specified by the + * datasheet. + * 3. Sends 'POWER_UP' command to the device with all provided + * information about power-up parameters. + * 4. Configures, pin multiplexor, disables digital audio and + * configures interrupt sources. + * + * The function returns zero in case of succes or negative error code + * otherwise. + */ +int si476x_core_start(struct si476x_core *core, bool soft) +{ + struct i2c_client *client = core->client; + int err; + + if (!soft) { + if (gpio_is_valid(core->gpio_reset)) + gpio_set_value_cansleep(core->gpio_reset, 1); + + if (client->irq) + enable_irq(client->irq); + + udelay(100); + + if (!client->irq) { + atomic_set(&core->is_alive, 1); + si476x_core_schedule_polling_work(core); + } + } else { + if (client->irq) + enable_irq(client->irq); + else { + atomic_set(&core->is_alive, 1); + si476x_core_schedule_polling_work(core); + } + } + + err = si476x_core_cmd_power_up(core, + &core->power_up_parameters); + + if (err < 0) { + dev_err(&core->client->dev, + "Power up failure(err = %d)\n", + err); + goto disable_irq; + } + + if (client->irq) + atomic_set(&core->is_alive, 1); + + err = si476x_core_config_pinmux(core); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure pinmux(err = %d)\n", + err); + goto disable_irq; + } + + if (client->irq) { + err = regmap_write(core->regmap, + SI476X_PROP_INT_CTL_ENABLE, + SI476X_RDSIEN | + SI476X_STCIEN | + SI476X_CTSIEN); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure interrupt sources" + "(err = %d)\n", err); + goto disable_irq; + } + } + + return 0; + +disable_irq: + if (err == -ENODEV) + atomic_set(&core->is_alive, 0); + + if (client->irq) + disable_irq(client->irq); + else + cancel_delayed_work_sync(&core->status_monitor); + + if (gpio_is_valid(core->gpio_reset)) + gpio_set_value_cansleep(core->gpio_reset, 0); + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_start); + +/** + * si476x_core_stop() - chip power-down function + * @core: Core device structure + * @soft: When set, function sends a POWER_DOWN command instead of + * bringing reset line low + * + * Power down the chip by performing following actions: + * 1. Disable IRQ or stop the polling worker + * 2. Send the POWER_DOWN command if the power down is soft or bring + * reset line low if not. + * + * The function returns zero in case of succes or negative error code + * otherwise. + */ +int si476x_core_stop(struct si476x_core *core, bool soft) +{ + int err = 0; + atomic_set(&core->is_alive, 0); + + if (soft) { + /* TODO: This probably shoud be a configurable option, + * so it is possible to have the chips keep their + * oscillators running + */ + struct si476x_power_down_args args = { + .xosc = false, + }; + err = si476x_core_cmd_power_down(core, &args); + } + + /* We couldn't disable those before + * 'si476x_core_cmd_power_down' since we expect to get CTS + * interrupt */ + if (core->client->irq) + disable_irq(core->client->irq); + else + cancel_delayed_work_sync(&core->status_monitor); + + if (!soft) { + if (gpio_is_valid(core->gpio_reset)) + gpio_set_value_cansleep(core->gpio_reset, 0); + } + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_stop); + +/** + * si476x_core_set_power_state() - set the level at which the power is + * supplied for the chip. + * @core: Core device structure + * @next_state: enum si476x_power_state describing power state to + * switch to. + * + * Switch on all the required power supplies + * + * This function returns 0 in case of suvccess and negative error code + * otherwise. + */ +int si476x_core_set_power_state(struct si476x_core *core, + enum si476x_power_state next_state) +{ + /* + It is not clear form the datasheet if it is possible to + work with device if not all power domains are operational. + So for now the power-up policy is "power-up all the things!" + */ + int err = 0; + + if (core->power_state == SI476X_POWER_INCONSISTENT) { + dev_err(&core->client->dev, + "The device in inconsistent power state\n"); + return -EINVAL; + } + + if (next_state != core->power_state) { + switch (next_state) { + case SI476X_POWER_UP_FULL: + err = regulator_bulk_enable(ARRAY_SIZE(core->supplies), + core->supplies); + if (err < 0) { + core->power_state = SI476X_POWER_INCONSISTENT; + break; + } + /* + * Startup timing diagram recommends to have a + * 100 us delay between enabling of the power + * supplies and turning the tuner on. + */ + udelay(100); + + err = si476x_core_start(core, false); + if (err < 0) + goto disable_regulators; + + core->power_state = next_state; + break; + + case SI476X_POWER_DOWN: + core->power_state = next_state; + err = si476x_core_stop(core, false); + if (err < 0) + core->power_state = SI476X_POWER_INCONSISTENT; +disable_regulators: + err = regulator_bulk_disable(ARRAY_SIZE(core->supplies), + core->supplies); + if (err < 0) + core->power_state = SI476X_POWER_INCONSISTENT; + break; + default: + BUG(); + } + } + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_set_power_state); + +/** + * si476x_core_report_drainer_stop() - mark the completion of the RDS + * buffer drain porcess by the worker. + * + * @core: Core device structure + */ +static inline void si476x_core_report_drainer_stop(struct si476x_core *core) +{ + mutex_lock(&core->rds_drainer_status_lock); + core->rds_drainer_is_working = false; + mutex_unlock(&core->rds_drainer_status_lock); +} + +/** + * si476x_core_start_rds_drainer_once() - start RDS drainer worker if + * ther is none working, do nothing otherwise + * + * @core: Datastructure corresponding to the chip. + */ +static inline void si476x_core_start_rds_drainer_once(struct si476x_core *core) +{ + mutex_lock(&core->rds_drainer_status_lock); + if (!core->rds_drainer_is_working) { + core->rds_drainer_is_working = true; + schedule_work(&core->rds_fifo_drainer); + } + mutex_unlock(&core->rds_drainer_status_lock); +} +/** + * si476x_drain_rds_fifo() - RDS buffer drainer. + * @work: struct work_struct being ppassed to the function by the + * kernel. + * + * Drain the contents of the RDS FIFO of + */ +static void si476x_core_drain_rds_fifo(struct work_struct *work) +{ + int err; + + struct si476x_core *core = container_of(work, struct si476x_core, + rds_fifo_drainer); + + struct si476x_rds_status_report report; + + si476x_core_lock(core); + err = si476x_core_cmd_fm_rds_status(core, true, false, false, &report); + if (!err) { + int i = report.rdsfifoused; + dev_dbg(&core->client->dev, + "%d elements in RDS FIFO. Draining.\n", i); + for (; i > 0; --i) { + err = si476x_core_cmd_fm_rds_status(core, false, false, + (i == 1), &report); + if (err < 0) + goto unlock; + + kfifo_in(&core->rds_fifo, report.rds, + sizeof(report.rds)); + dev_dbg(&core->client->dev, "RDS data:\n %*ph\n", + (int)sizeof(report.rds), report.rds); + } + dev_dbg(&core->client->dev, "Drrrrained!\n"); + wake_up_interruptible(&core->rds_read_queue); + } + +unlock: + si476x_core_unlock(core); + si476x_core_report_drainer_stop(core); +} + +/** + * si476x_core_pronounce_dead() + * + * @core: Core device structure + * + * Mark the device as being dead and wake up all potentially waiting + * threads of execution. + * + */ +static void si476x_core_pronounce_dead(struct si476x_core *core) +{ + dev_info(&core->client->dev, "Core device is dead.\n"); + + atomic_set(&core->is_alive, 0); + + /* Wake up al possible waiting processes */ + wake_up_interruptible(&core->rds_read_queue); + + atomic_set(&core->cts, 1); + wake_up(&core->command); + + atomic_set(&core->stc, 1); + wake_up(&core->tuning); +} + +/** + * si476x_core_i2c_xfer() + * + * @core: Core device structure + * @type: Transfer type + * @buf: Transfer buffer for/with data + * @count: Transfer buffer size + * + * Perfrom and I2C transfer(either read or write) and keep a counter + * of I/O errors. If the error counter rises above the threshold + * pronounce device dead. + * + * The function returns zero on succes or negative error code on + * failure. + */ +int si476x_core_i2c_xfer(struct si476x_core *core, + enum si476x_i2c_type type, + char *buf, int count) +{ + static int io_errors_count; + int err; + if (type == SI476X_I2C_SEND) + err = i2c_master_send(core->client, buf, count); + else + err = i2c_master_recv(core->client, buf, count); + + if (err < 0) { + if (io_errors_count++ > SI476X_MAX_IO_ERRORS) + si476x_core_pronounce_dead(core); + } else { + io_errors_count = 0; + } + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_i2c_xfer); + +/** + * si476x_get_status() + * @core: Core device structure + * + * Get the status byte of the core device by berforming one byte I2C + * read. + * + * The function returns a status value or a negative error code on + * error. + */ +static int si476x_core_get_status(struct si476x_core *core) +{ + u8 response; + int err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV, + &response, sizeof(response)); + + return (err < 0) ? err : response; +} + +/** + * si476x_get_and_signal_status() - IRQ dispatcher + * @core: Core device structure + * + * Dispatch the arrived interrupt request based on the value of the + * status byte reported by the tuner. + * + */ +static void si476x_core_get_and_signal_status(struct si476x_core *core) +{ + int status = si476x_core_get_status(core); + if (status < 0) { + dev_err(&core->client->dev, "Failed to get status\n"); + return; + } + + if (status & SI476X_CTS) { + /* Unfortunately completions could not be used for + * signalling CTS since this flag cannot be cleared + * in status byte, and therefore once it becomes true + * multiple calls to 'complete' would cause the + * commands following the current one to be completed + * before they actually are */ + dev_dbg(&core->client->dev, "[interrupt] CTSINT\n"); + atomic_set(&core->cts, 1); + wake_up(&core->command); + } + + if (status & SI476X_FM_RDS_INT) { + dev_dbg(&core->client->dev, "[interrupt] RDSINT\n"); + si476x_core_start_rds_drainer_once(core); + } + + if (status & SI476X_STC_INT) { + dev_dbg(&core->client->dev, "[interrupt] STCINT\n"); + atomic_set(&core->stc, 1); + wake_up(&core->tuning); + } +} + +static void si476x_core_poll_loop(struct work_struct *work) +{ + struct si476x_core *core = SI476X_WORK_TO_CORE(work); + + si476x_core_get_and_signal_status(core); + + if (atomic_read(&core->is_alive)) + si476x_core_schedule_polling_work(core); +} + +static irqreturn_t si476x_core_interrupt(int irq, void *dev) +{ + struct si476x_core *core = dev; + + si476x_core_get_and_signal_status(core); + + return IRQ_HANDLED; +} + +/** + * si476x_firmware_version_to_revision() + * @core: Core device structure + * @major: Firmware major number + * @minor1: Firmware first minor number + * @minor2: Firmware second minor number + * + * Convert a chip's firmware version number into an offset that later + * will be used to as offset in "vtable" of tuner functions + * + * This function returns a positive offset in case of success and a -1 + * in case of failure. + */ +static int si476x_core_fwver_to_revision(struct si476x_core *core, + int func, int major, + int minor1, int minor2) +{ + switch (func) { + case SI476X_FUNC_FM_RECEIVER: + switch (major) { + case 5: + return SI476X_REVISION_A10; + case 8: + return SI476X_REVISION_A20; + case 10: + return SI476X_REVISION_A30; + default: + goto unknown_revision; + } + case SI476X_FUNC_AM_RECEIVER: + switch (major) { + case 5: + return SI476X_REVISION_A10; + case 7: + return SI476X_REVISION_A20; + case 9: + return SI476X_REVISION_A30; + default: + goto unknown_revision; + } + case SI476X_FUNC_WB_RECEIVER: + switch (major) { + case 3: + return SI476X_REVISION_A10; + case 5: + return SI476X_REVISION_A20; + case 7: + return SI476X_REVISION_A30; + default: + goto unknown_revision; + } + case SI476X_FUNC_BOOTLOADER: + default: /* FALLTHROUG */ + BUG(); + return -1; + } + +unknown_revision: + dev_err(&core->client->dev, + "Unsupported version of the firmware: %d.%d.%d, " + "reverting to A10 comptible functions\n", + major, minor1, minor2); + + return SI476X_REVISION_A10; +} + +/** + * si476x_get_revision_info() + * @core: Core device structure + * + * Get the firmware version number of the device. It is done in + * following three steps: + * 1. Power-up the device + * 2. Send the 'FUNC_INFO' command + * 3. Powering the device down. + * + * The function return zero on success and a negative error code on + * failure. + */ +static int si476x_core_get_revision_info(struct si476x_core *core) +{ + int rval; + struct si476x_func_info info; + + si476x_core_lock(core); + rval = si476x_core_set_power_state(core, SI476X_POWER_UP_FULL); + if (rval < 0) + goto exit; + + rval = si476x_core_cmd_func_info(core, &info); + if (rval < 0) + goto power_down; + + core->revision = si476x_core_fwver_to_revision(core, info.func, + info.firmware.major, + info.firmware.minor[0], + info.firmware.minor[1]); +power_down: + si476x_core_set_power_state(core, SI476X_POWER_DOWN); +exit: + si476x_core_unlock(core); + + return rval; +} + +bool si476x_core_has_am(struct si476x_core *core) +{ + return core->chip_id == SI476X_CHIP_SI4761 || + core->chip_id == SI476X_CHIP_SI4764; +} +EXPORT_SYMBOL_GPL(si476x_core_has_am); + +bool si476x_core_has_diversity(struct si476x_core *core) +{ + return core->chip_id == SI476X_CHIP_SI4764; +} +EXPORT_SYMBOL_GPL(si476x_core_has_diversity); + +bool si476x_core_is_a_secondary_tuner(struct si476x_core *core) +{ + return si476x_core_has_diversity(core) && + (core->diversity_mode == SI476X_PHDIV_SECONDARY_ANTENNA || + core->diversity_mode == SI476X_PHDIV_SECONDARY_COMBINING); +} +EXPORT_SYMBOL_GPL(si476x_core_is_a_secondary_tuner); + +bool si476x_core_is_a_primary_tuner(struct si476x_core *core) +{ + return si476x_core_has_diversity(core) && + (core->diversity_mode == SI476X_PHDIV_PRIMARY_ANTENNA || + core->diversity_mode == SI476X_PHDIV_PRIMARY_COMBINING); +} +EXPORT_SYMBOL_GPL(si476x_core_is_a_primary_tuner); + +bool si476x_core_is_in_am_receiver_mode(struct si476x_core *core) +{ + return si476x_core_has_am(core) && + (core->power_up_parameters.func == SI476X_FUNC_AM_RECEIVER); +} +EXPORT_SYMBOL_GPL(si476x_core_is_in_am_receiver_mode); + +bool si476x_core_is_powered_up(struct si476x_core *core) +{ + return core->power_state == SI476X_POWER_UP_FULL; +} +EXPORT_SYMBOL_GPL(si476x_core_is_powered_up); + +static int si476x_core_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rval; + struct si476x_core *core; + struct si476x_platform_data *pdata; + struct mfd_cell *cell; + int cell_num; + + core = devm_kzalloc(&client->dev, sizeof(*core), GFP_KERNEL); + if (!core) { + dev_err(&client->dev, + "failed to allocate 'struct si476x_core'\n"); + return -ENOMEM; + } + core->client = client; + + core->regmap = devm_regmap_init_si476x(core); + if (IS_ERR(core->regmap)) { + rval = PTR_ERR(core->regmap); + dev_err(&client->dev, + "Failed to allocate register map: %d\n", + rval); + return rval; + } + + i2c_set_clientdata(client, core); + + atomic_set(&core->is_alive, 0); + core->power_state = SI476X_POWER_DOWN; + + pdata = client->dev.platform_data; + if (pdata) { + memcpy(&core->power_up_parameters, + &pdata->power_up_parameters, + sizeof(core->power_up_parameters)); + + core->gpio_reset = -1; + if (gpio_is_valid(pdata->gpio_reset)) { + rval = gpio_request(pdata->gpio_reset, "si476x reset"); + if (rval) { + dev_err(&client->dev, + "Failed to request gpio: %d\n", rval); + return rval; + } + core->gpio_reset = pdata->gpio_reset; + gpio_direction_output(core->gpio_reset, 0); + } + + core->diversity_mode = pdata->diversity_mode; + memcpy(&core->pinmux, &pdata->pinmux, + sizeof(struct si476x_pinmux)); + } else { + dev_err(&client->dev, "No platform data provided\n"); + return -EINVAL; + } + + core->supplies[0].supply = "vd"; + core->supplies[1].supply = "va"; + core->supplies[2].supply = "vio1"; + core->supplies[3].supply = "vio2"; + + rval = devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(core->supplies), + core->supplies); + if (rval) { + dev_err(&client->dev, "Failet to gett all of the regulators\n"); + goto free_gpio; + } + + mutex_init(&core->cmd_lock); + init_waitqueue_head(&core->command); + init_waitqueue_head(&core->tuning); + + rval = kfifo_alloc(&core->rds_fifo, + SI476X_DRIVER_RDS_FIFO_DEPTH * + sizeof(struct v4l2_rds_data), + GFP_KERNEL); + if (rval) { + dev_err(&client->dev, "Could not alloate the FIFO\n"); + goto free_gpio; + } + mutex_init(&core->rds_drainer_status_lock); + init_waitqueue_head(&core->rds_read_queue); + INIT_WORK(&core->rds_fifo_drainer, si476x_core_drain_rds_fifo); + + if (client->irq) { + rval = devm_request_threaded_irq(&client->dev, + client->irq, NULL, + si476x_core_interrupt, + IRQF_TRIGGER_FALLING, + client->name, core); + if (rval < 0) { + dev_err(&client->dev, "Could not request IRQ %d\n", + client->irq); + goto free_kfifo; + } + disable_irq(client->irq); + dev_dbg(&client->dev, "IRQ requested.\n"); + + core->rds_fifo_depth = 20; + } else { + INIT_DELAYED_WORK(&core->status_monitor, + si476x_core_poll_loop); + dev_info(&client->dev, + "No IRQ number specified, will use polling\n"); + + core->rds_fifo_depth = 5; + } + + core->chip_id = id->driver_data; + + rval = si476x_core_get_revision_info(core); + if (rval < 0) { + rval = -ENODEV; + goto free_kfifo; + } + + cell_num = 0; + + cell = &core->cells[SI476X_RADIO_CELL]; + cell->name = "si476x-radio"; + cell_num++; + +#ifdef CONFIG_SND_SOC_SI476X + if ((core->chip_id == SI476X_CHIP_SI4761 || + core->chip_id == SI476X_CHIP_SI4764) && + core->pinmux.dclk == SI476X_DCLK_DAUDIO && + core->pinmux.dfs == SI476X_DFS_DAUDIO && + core->pinmux.dout == SI476X_DOUT_I2S_OUTPUT && + core->pinmux.xout == SI476X_XOUT_TRISTATE) { + cell = &core->cells[SI476X_CODEC_CELL]; + cell->name = "si476x-codec"; + cell_num++; + } +#endif + rval = mfd_add_devices(&client->dev, + (client->adapter->nr << 8) + client->addr, + core->cells, cell_num, + NULL, 0, NULL); + if (!rval) + return 0; + +free_kfifo: + kfifo_free(&core->rds_fifo); + +free_gpio: + if (gpio_is_valid(core->gpio_reset)) + gpio_free(core->gpio_reset); + + return rval; +} + +static int si476x_core_remove(struct i2c_client *client) +{ + struct si476x_core *core = i2c_get_clientdata(client); + + si476x_core_pronounce_dead(core); + mfd_remove_devices(&client->dev); + + if (client->irq) + disable_irq(client->irq); + else + cancel_delayed_work_sync(&core->status_monitor); + + kfifo_free(&core->rds_fifo); + + if (gpio_is_valid(core->gpio_reset)) + gpio_free(core->gpio_reset); + + return 0; +} + + +static const struct i2c_device_id si476x_id[] = { + { "si4761", SI476X_CHIP_SI4761 }, + { "si4764", SI476X_CHIP_SI4764 }, + { "si4768", SI476X_CHIP_SI4768 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, si476x_id); + +static struct i2c_driver si476x_core_driver = { + .driver = { + .name = "si476x-core", + .owner = THIS_MODULE, + }, + .probe = si476x_core_probe, + .remove = si476x_core_remove, + .id_table = si476x_id, +}; +module_i2c_driver(si476x_core_driver); + + +MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); +MODULE_DESCRIPTION("Si4761/64/68 AM/FM MFD core device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/si476x-prop.c b/drivers/mfd/si476x-prop.c new file mode 100644 index 000000000000..cfeffa6e15d9 --- /dev/null +++ b/drivers/mfd/si476x-prop.c @@ -0,0 +1,241 @@ +/* + * drivers/mfd/si476x-prop.c -- Subroutines to access + * properties of si476x chips + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov <andrew.smirnov@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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include <linux/module.h> + +#include <linux/mfd/si476x-core.h> + +struct si476x_property_range { + u16 low, high; +}; + +static bool si476x_core_element_is_in_array(u16 element, + const u16 array[], + size_t size) +{ + int i; + + for (i = 0; i < size; i++) + if (element == array[i]) + return true; + + return false; +} + +static bool si476x_core_element_is_in_range(u16 element, + const struct si476x_property_range range[], + size_t size) +{ + int i; + + for (i = 0; i < size; i++) + if (element <= range[i].high && element >= range[i].low) + return true; + + return false; +} + +static bool si476x_core_is_valid_property_a10(struct si476x_core *core, + u16 property) +{ + static const u16 valid_properties[] = { + 0x0000, + 0x0500, 0x0501, + 0x0600, + 0x0709, 0x070C, 0x070D, 0x70E, 0x710, + 0x0718, + 0x1207, 0x1208, + 0x2007, + 0x2300, + }; + + static const struct si476x_property_range valid_ranges[] = { + { 0x0200, 0x0203 }, + { 0x0300, 0x0303 }, + { 0x0400, 0x0404 }, + { 0x0700, 0x0707 }, + { 0x1100, 0x1102 }, + { 0x1200, 0x1204 }, + { 0x1300, 0x1306 }, + { 0x2000, 0x2005 }, + { 0x2100, 0x2104 }, + { 0x2106, 0x2106 }, + { 0x2200, 0x220E }, + { 0x3100, 0x3104 }, + { 0x3207, 0x320F }, + { 0x3300, 0x3304 }, + { 0x3500, 0x3517 }, + { 0x3600, 0x3617 }, + { 0x3700, 0x3717 }, + { 0x4000, 0x4003 }, + }; + + return si476x_core_element_is_in_range(property, valid_ranges, + ARRAY_SIZE(valid_ranges)) || + si476x_core_element_is_in_array(property, valid_properties, + ARRAY_SIZE(valid_properties)); +} + +static bool si476x_core_is_valid_property_a20(struct si476x_core *core, + u16 property) +{ + static const u16 valid_properties[] = { + 0x071B, + 0x1006, + 0x2210, + 0x3401, + }; + + static const struct si476x_property_range valid_ranges[] = { + { 0x2215, 0x2219 }, + }; + + return si476x_core_is_valid_property_a10(core, property) || + si476x_core_element_is_in_range(property, valid_ranges, + ARRAY_SIZE(valid_ranges)) || + si476x_core_element_is_in_array(property, valid_properties, + ARRAY_SIZE(valid_properties)); +} + +static bool si476x_core_is_valid_property_a30(struct si476x_core *core, + u16 property) +{ + static const u16 valid_properties[] = { + 0x071C, 0x071D, + 0x1007, 0x1008, + 0x220F, 0x2214, + 0x2301, + 0x3105, 0x3106, + 0x3402, + }; + + static const struct si476x_property_range valid_ranges[] = { + { 0x0405, 0x0411 }, + { 0x2008, 0x200B }, + { 0x2220, 0x2223 }, + { 0x3100, 0x3106 }, + }; + + return si476x_core_is_valid_property_a20(core, property) || + si476x_core_element_is_in_range(property, valid_ranges, + ARRAY_SIZE(valid_ranges)) || + si476x_core_element_is_in_array(property, valid_properties, + ARRAY_SIZE(valid_properties)); +} + +typedef bool (*valid_property_pred_t) (struct si476x_core *, u16); + +static bool si476x_core_is_valid_property(struct si476x_core *core, + u16 property) +{ + static const valid_property_pred_t is_valid_property[] = { + [SI476X_REVISION_A10] = si476x_core_is_valid_property_a10, + [SI476X_REVISION_A20] = si476x_core_is_valid_property_a20, + [SI476X_REVISION_A30] = si476x_core_is_valid_property_a30, + }; + + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return is_valid_property[core->revision](core, property); +} + + +static bool si476x_core_is_readonly_property(struct si476x_core *core, + u16 property) +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + + switch (core->revision) { + case SI476X_REVISION_A10: + return (property == 0x3200); + case SI476X_REVISION_A20: + return (property == 0x1006 || + property == 0x2210 || + property == 0x3200); + case SI476X_REVISION_A30: + return false; + } + + return false; +} + +static bool si476x_core_regmap_readable_register(struct device *dev, + unsigned int reg) +{ + struct i2c_client *client = to_i2c_client(dev); + struct si476x_core *core = i2c_get_clientdata(client); + + return si476x_core_is_valid_property(core, (u16) reg); + +} + +static bool si476x_core_regmap_writable_register(struct device *dev, + unsigned int reg) +{ + struct i2c_client *client = to_i2c_client(dev); + struct si476x_core *core = i2c_get_clientdata(client); + + return si476x_core_is_valid_property(core, (u16) reg) && + !si476x_core_is_readonly_property(core, (u16) reg); +} + + +static int si476x_core_regmap_write(void *context, unsigned int reg, + unsigned int val) +{ + return si476x_core_cmd_set_property(context, reg, val); +} + +static int si476x_core_regmap_read(void *context, unsigned int reg, + unsigned *val) +{ + struct si476x_core *core = context; + int err; + + err = si476x_core_cmd_get_property(core, reg); + if (err < 0) + return err; + + *val = err; + + return 0; +} + + +static const struct regmap_config si476x_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + + .max_register = 0x4003, + + .writeable_reg = si476x_core_regmap_writable_register, + .readable_reg = si476x_core_regmap_readable_register, + + .reg_read = si476x_core_regmap_read, + .reg_write = si476x_core_regmap_write, + + .cache_type = REGCACHE_RBTREE, +}; + +struct regmap *devm_regmap_init_si476x(struct si476x_core *core) +{ + return devm_regmap_init(&core->client->dev, NULL, + core, &si476x_regmap_config); +} +EXPORT_SYMBOL_GPL(devm_regmap_init_si476x); diff --git a/drivers/mfd/sta2x11-mfd.c b/drivers/mfd/sta2x11-mfd.c index 9bd33169a111..d70a343078fd 100644 --- a/drivers/mfd/sta2x11-mfd.c +++ b/drivers/mfd/sta2x11-mfd.c @@ -98,17 +98,6 @@ static int sta2x11_mfd_add(struct pci_dev *pdev, gfp_t flags) return 0; } -static int mfd_remove(struct pci_dev *pdev) -{ - struct sta2x11_mfd *mfd = sta2x11_mfd_find(pdev); - - if (!mfd) - return -ENODEV; - list_del(&mfd->list); - kfree(mfd); - return 0; -} - /* This function is exported and is not expected to fail */ u32 __sta2x11_mfd_mask(struct pci_dev *pdev, u32 reg, u32 mask, u32 val, enum sta2x11_mfd_plat_dev index) diff --git a/drivers/mfd/stmpe-i2c.c b/drivers/mfd/stmpe-i2c.c index fd5fcb630685..0da02e11d58e 100644 --- a/drivers/mfd/stmpe-i2c.c +++ b/drivers/mfd/stmpe-i2c.c @@ -75,6 +75,7 @@ static const struct i2c_device_id stmpe_i2c_id[] = { { "stmpe801", STMPE801 }, { "stmpe811", STMPE811 }, { "stmpe1601", STMPE1601 }, + { "stmpe1801", STMPE1801 }, { "stmpe2401", STMPE2401 }, { "stmpe2403", STMPE2403 }, { } diff --git a/drivers/mfd/stmpe-spi.c b/drivers/mfd/stmpe-spi.c index 973659f8abd9..a81badbaa917 100644 --- a/drivers/mfd/stmpe-spi.c +++ b/drivers/mfd/stmpe-spi.c @@ -103,7 +103,7 @@ stmpe_spi_probe(struct spi_device *spi) static int stmpe_spi_remove(struct spi_device *spi) { - struct stmpe *stmpe = dev_get_drvdata(&spi->dev); + struct stmpe *stmpe = spi_get_drvdata(spi); return stmpe_remove(stmpe); } diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index 4b11202061be..bbccd514d3ec 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -19,6 +19,7 @@ #include <linux/pm.h> #include <linux/slab.h> #include <linux/mfd/core.h> +#include <linux/delay.h> #include "stmpe.h" static int __stmpe_enable(struct stmpe *stmpe, unsigned int blocks) @@ -643,6 +644,88 @@ static struct stmpe_variant_info stmpe1601 = { }; /* + * STMPE1801 + */ +static const u8 stmpe1801_regs[] = { + [STMPE_IDX_CHIP_ID] = STMPE1801_REG_CHIP_ID, + [STMPE_IDX_ICR_LSB] = STMPE1801_REG_INT_CTRL_LOW, + [STMPE_IDX_IER_LSB] = STMPE1801_REG_INT_EN_MASK_LOW, + [STMPE_IDX_ISR_LSB] = STMPE1801_REG_INT_STA_LOW, + [STMPE_IDX_GPMR_LSB] = STMPE1801_REG_GPIO_MP_LOW, + [STMPE_IDX_GPSR_LSB] = STMPE1801_REG_GPIO_SET_LOW, + [STMPE_IDX_GPCR_LSB] = STMPE1801_REG_GPIO_CLR_LOW, + [STMPE_IDX_GPDR_LSB] = STMPE1801_REG_GPIO_SET_DIR_LOW, + [STMPE_IDX_GPRER_LSB] = STMPE1801_REG_GPIO_RE_LOW, + [STMPE_IDX_GPFER_LSB] = STMPE1801_REG_GPIO_FE_LOW, + [STMPE_IDX_IEGPIOR_LSB] = STMPE1801_REG_INT_EN_GPIO_MASK_LOW, + [STMPE_IDX_ISGPIOR_LSB] = STMPE1801_REG_INT_STA_GPIO_LOW, +}; + +static struct stmpe_variant_block stmpe1801_blocks[] = { + { + .cell = &stmpe_gpio_cell, + .irq = STMPE1801_IRQ_GPIOC, + .block = STMPE_BLOCK_GPIO, + }, + { + .cell = &stmpe_keypad_cell, + .irq = STMPE1801_IRQ_KEYPAD, + .block = STMPE_BLOCK_KEYPAD, + }, +}; + +static int stmpe1801_enable(struct stmpe *stmpe, unsigned int blocks, + bool enable) +{ + unsigned int mask = 0; + if (blocks & STMPE_BLOCK_GPIO) + mask |= STMPE1801_MSK_INT_EN_GPIO; + + if (blocks & STMPE_BLOCK_KEYPAD) + mask |= STMPE1801_MSK_INT_EN_KPC; + + return __stmpe_set_bits(stmpe, STMPE1801_REG_INT_EN_MASK_LOW, mask, + enable ? mask : 0); +} + +static int stmpe1801_reset(struct stmpe *stmpe) +{ + unsigned long timeout; + int ret = 0; + + ret = __stmpe_set_bits(stmpe, STMPE1801_REG_SYS_CTRL, + STMPE1801_MSK_SYS_CTRL_RESET, STMPE1801_MSK_SYS_CTRL_RESET); + if (ret < 0) + return ret; + + timeout = jiffies + msecs_to_jiffies(100); + while (time_before(jiffies, timeout)) { + ret = __stmpe_reg_read(stmpe, STMPE1801_REG_SYS_CTRL); + if (ret < 0) + return ret; + if (!(ret & STMPE1801_MSK_SYS_CTRL_RESET)) + return 0; + usleep_range(100, 200); + }; + return -EIO; +} + +static struct stmpe_variant_info stmpe1801 = { + .name = "stmpe1801", + .id_val = STMPE1801_ID, + .id_mask = 0xfff0, + .num_gpios = 18, + .af_bits = 0, + .regs = stmpe1801_regs, + .blocks = stmpe1801_blocks, + .num_blocks = ARRAY_SIZE(stmpe1801_blocks), + .num_irqs = STMPE1801_NR_INTERNAL_IRQS, + .enable = stmpe1801_enable, + /* stmpe1801 do not have any gpio alternate function */ + .get_altfunc = NULL, +}; + +/* * STMPE24XX */ @@ -740,6 +823,7 @@ static struct stmpe_variant_info *stmpe_variant_info[STMPE_NBR_PARTS] = { [STMPE801] = &stmpe801, [STMPE811] = &stmpe811, [STMPE1601] = &stmpe1601, + [STMPE1801] = &stmpe1801, [STMPE2401] = &stmpe2401, [STMPE2403] = &stmpe2403, }; @@ -759,7 +843,7 @@ static irqreturn_t stmpe_irq(int irq, void *data) struct stmpe *stmpe = data; struct stmpe_variant_info *variant = stmpe->variant; int num = DIV_ROUND_UP(variant->num_irqs, 8); - u8 israddr = stmpe->regs[STMPE_IDX_ISR_MSB]; + u8 israddr; u8 isr[num]; int ret; int i; @@ -771,6 +855,11 @@ static irqreturn_t stmpe_irq(int irq, void *data) return IRQ_HANDLED; } + if (variant->id_val == STMPE1801_ID) + israddr = stmpe->regs[STMPE_IDX_ISR_LSB]; + else + israddr = stmpe->regs[STMPE_IDX_ISR_MSB]; + ret = stmpe_block_read(stmpe, israddr, num, isr); if (ret < 0) return IRQ_NONE; @@ -938,6 +1027,12 @@ static int stmpe_chip_init(struct stmpe *stmpe) if (ret) return ret; + if (id == STMPE1801_ID) { + ret = stmpe1801_reset(stmpe); + if (ret < 0) + return ret; + } + if (stmpe->irq >= 0) { if (id == STMPE801_ID) icr = STMPE801_REG_SYS_CTRL_INT_EN; @@ -1015,7 +1110,10 @@ void stmpe_of_probe(struct stmpe_platform_data *pdata, struct device_node *np) { struct device_node *child; - pdata->id = -1; + pdata->id = of_alias_get_id(np, "stmpe-i2c"); + if (pdata->id < 0) + pdata->id = -1; + pdata->irq_trigger = IRQF_TRIGGER_NONE; of_property_read_u32(np, "st,autosleep-timeout", @@ -1057,6 +1155,9 @@ int stmpe_probe(struct stmpe_client_info *ci, int partnum) return -ENOMEM; stmpe_of_probe(pdata, np); + + if (of_find_property(np, "interrupts", NULL) == NULL) + ci->irq = -1; } stmpe = devm_kzalloc(ci->dev, sizeof(struct stmpe), GFP_KERNEL); diff --git a/drivers/mfd/stmpe.h b/drivers/mfd/stmpe.h index 7b8e13f5b764..ff2b09ba8797 100644 --- a/drivers/mfd/stmpe.h +++ b/drivers/mfd/stmpe.h @@ -199,6 +199,55 @@ int stmpe_remove(struct stmpe *stmpe); #define STPME1601_AUTOSLEEP_ENABLE (1 << 3) /* + * STMPE1801 + */ +#define STMPE1801_ID 0xc110 +#define STMPE1801_NR_INTERNAL_IRQS 5 +#define STMPE1801_IRQ_KEYPAD_COMBI 4 +#define STMPE1801_IRQ_GPIOC 3 +#define STMPE1801_IRQ_KEYPAD_OVER 2 +#define STMPE1801_IRQ_KEYPAD 1 +#define STMPE1801_IRQ_WAKEUP 0 + +#define STMPE1801_REG_CHIP_ID 0x00 +#define STMPE1801_REG_SYS_CTRL 0x02 +#define STMPE1801_REG_INT_CTRL_LOW 0x04 +#define STMPE1801_REG_INT_EN_MASK_LOW 0x06 +#define STMPE1801_REG_INT_STA_LOW 0x08 +#define STMPE1801_REG_INT_EN_GPIO_MASK_LOW 0x0A +#define STMPE1801_REG_INT_EN_GPIO_MASK_MID 0x0B +#define STMPE1801_REG_INT_EN_GPIO_MASK_HIGH 0x0C +#define STMPE1801_REG_INT_STA_GPIO_LOW 0x0D +#define STMPE1801_REG_INT_STA_GPIO_MID 0x0E +#define STMPE1801_REG_INT_STA_GPIO_HIGH 0x0F +#define STMPE1801_REG_GPIO_SET_LOW 0x10 +#define STMPE1801_REG_GPIO_SET_MID 0x11 +#define STMPE1801_REG_GPIO_SET_HIGH 0x12 +#define STMPE1801_REG_GPIO_CLR_LOW 0x13 +#define STMPE1801_REG_GPIO_CLR_MID 0x14 +#define STMPE1801_REG_GPIO_CLR_HIGH 0x15 +#define STMPE1801_REG_GPIO_MP_LOW 0x16 +#define STMPE1801_REG_GPIO_MP_MID 0x17 +#define STMPE1801_REG_GPIO_MP_HIGH 0x18 +#define STMPE1801_REG_GPIO_SET_DIR_LOW 0x19 +#define STMPE1801_REG_GPIO_SET_DIR_MID 0x1A +#define STMPE1801_REG_GPIO_SET_DIR_HIGH 0x1B +#define STMPE1801_REG_GPIO_RE_LOW 0x1C +#define STMPE1801_REG_GPIO_RE_MID 0x1D +#define STMPE1801_REG_GPIO_RE_HIGH 0x1E +#define STMPE1801_REG_GPIO_FE_LOW 0x1F +#define STMPE1801_REG_GPIO_FE_MID 0x20 +#define STMPE1801_REG_GPIO_FE_HIGH 0x21 +#define STMPE1801_REG_GPIO_PULL_UP_LOW 0x22 +#define STMPE1801_REG_GPIO_PULL_UP_MID 0x23 +#define STMPE1801_REG_GPIO_PULL_UP_HIGH 0x24 + +#define STMPE1801_MSK_SYS_CTRL_RESET (1 << 7) + +#define STMPE1801_MSK_INT_EN_KPC (1 << 1) +#define STMPE1801_MSK_INT_EN_GPIO (1 << 3) + +/* * STMPE24xx */ diff --git a/drivers/mfd/syscon.c b/drivers/mfd/syscon.c index 61aea6381cdf..962a6e17a01a 100644 --- a/drivers/mfd/syscon.c +++ b/drivers/mfd/syscon.c @@ -25,17 +25,15 @@ static struct platform_driver syscon_driver; struct syscon { - struct device *dev; void __iomem *base; struct regmap *regmap; }; -static int syscon_match(struct device *dev, void *data) +static int syscon_match_node(struct device *dev, void *data) { - struct syscon *syscon = dev_get_drvdata(dev); struct device_node *dn = data; - return (syscon->dev->of_node == dn) ? 1 : 0; + return (dev->of_node == dn) ? 1 : 0; } struct regmap *syscon_node_to_regmap(struct device_node *np) @@ -44,7 +42,7 @@ struct regmap *syscon_node_to_regmap(struct device_node *np) struct device *dev; dev = driver_find_device(&syscon_driver.driver, NULL, np, - syscon_match); + syscon_match_node); if (!dev) return ERR_PTR(-EPROBE_DEFER); @@ -70,6 +68,34 @@ struct regmap *syscon_regmap_lookup_by_compatible(const char *s) } EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible); +static int syscon_match_pdevname(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + const struct platform_device_id *id = platform_get_device_id(pdev); + + if (id) + if (!strcmp(id->name, (const char *)data)) + return 1; + + return !strcmp(dev_name(dev), (const char *)data); +} + +struct regmap *syscon_regmap_lookup_by_pdevname(const char *s) +{ + struct device *dev; + struct syscon *syscon; + + dev = driver_find_device(&syscon_driver.driver, NULL, (void *)s, + syscon_match_pdevname); + if (!dev) + return ERR_PTR(-EPROBE_DEFER); + + syscon = dev_get_drvdata(dev); + + return syscon->regmap; +} +EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_pdevname); + struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np, const char *property) { @@ -101,28 +127,22 @@ static struct regmap_config syscon_regmap_config = { static int syscon_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; struct syscon *syscon; - struct resource res; - int ret; - - if (!np) - return -ENOENT; + struct resource *res; - syscon = devm_kzalloc(dev, sizeof(struct syscon), - GFP_KERNEL); + syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL); if (!syscon) return -ENOMEM; - syscon->base = of_iomap(np, 0); - if (!syscon->base) - return -EADDRNOTAVAIL; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; - ret = of_address_to_resource(np, 0, &res); - if (ret) - return ret; + syscon->base = devm_ioremap(dev, res->start, resource_size(res)); + if (!syscon->base) + return -ENOMEM; - syscon_regmap_config.max_register = res.end - res.start - 3; + syscon_regmap_config.max_register = res->end - res->start - 3; syscon->regmap = devm_regmap_init_mmio(dev, syscon->base, &syscon_regmap_config); if (IS_ERR(syscon->regmap)) { @@ -130,25 +150,17 @@ static int syscon_probe(struct platform_device *pdev) return PTR_ERR(syscon->regmap); } - syscon->dev = dev; platform_set_drvdata(pdev, syscon); - dev_info(dev, "syscon regmap start 0x%x end 0x%x registered\n", - res.start, res.end); + dev_info(dev, "regmap %pR registered\n", res); return 0; } -static int syscon_remove(struct platform_device *pdev) -{ - struct syscon *syscon; - - syscon = platform_get_drvdata(pdev); - iounmap(syscon->base); - platform_set_drvdata(pdev, NULL); - - return 0; -} +static const struct platform_device_id syscon_ids[] = { + { "syscon", }, + { } +}; static struct platform_driver syscon_driver = { .driver = { @@ -157,7 +169,7 @@ static struct platform_driver syscon_driver = { .of_match_table = of_syscon_match, }, .probe = syscon_probe, - .remove = syscon_remove, + .id_table = syscon_ids, }; static int __init syscon_init(void) diff --git a/drivers/mfd/tc3589x.c b/drivers/mfd/tc3589x.c index ecc092c7f745..4cb92bb2aea2 100644 --- a/drivers/mfd/tc3589x.c +++ b/drivers/mfd/tc3589x.c @@ -350,7 +350,8 @@ static int tc3589x_probe(struct i2c_client *i2c, | I2C_FUNC_SMBUS_I2C_BLOCK)) return -EIO; - tc3589x = kzalloc(sizeof(struct tc3589x), GFP_KERNEL); + tc3589x = devm_kzalloc(&i2c->dev, sizeof(struct tc3589x), + GFP_KERNEL); if (!tc3589x) return -ENOMEM; @@ -366,33 +367,27 @@ static int tc3589x_probe(struct i2c_client *i2c, ret = tc3589x_chip_init(tc3589x); if (ret) - goto out_free; + return ret; ret = tc3589x_irq_init(tc3589x, np); if (ret) - goto out_free; + return ret; ret = request_threaded_irq(tc3589x->i2c->irq, NULL, tc3589x_irq, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "tc3589x", tc3589x); if (ret) { dev_err(tc3589x->dev, "failed to request IRQ: %d\n", ret); - goto out_free; + return ret; } ret = tc3589x_device_init(tc3589x); if (ret) { dev_err(tc3589x->dev, "failed to add child devices\n"); - goto out_freeirq; + return ret; } return 0; - -out_freeirq: - free_irq(tc3589x->i2c->irq, tc3589x); -out_free: - kfree(tc3589x); - return ret; } static int tc3589x_remove(struct i2c_client *client) @@ -401,10 +396,6 @@ static int tc3589x_remove(struct i2c_client *client) mfd_remove_devices(tc3589x->dev); - free_irq(tc3589x->i2c->irq, tc3589x); - - kfree(tc3589x); - return 0; } diff --git a/drivers/mfd/tps65090.c b/drivers/mfd/tps65090.c index 98edb5be85c6..fbd6ee67b5a5 100644 --- a/drivers/mfd/tps65090.c +++ b/drivers/mfd/tps65090.c @@ -56,12 +56,23 @@ #define TPS65090_INT2_MASK_OVERLOAD_FET6 6 #define TPS65090_INT2_MASK_OVERLOAD_FET7 7 +static struct resource charger_resources[] = { + { + .start = TPS65090_IRQ_VAC_STATUS_CHANGE, + .end = TPS65090_IRQ_VAC_STATUS_CHANGE, + .flags = IORESOURCE_IRQ, + } +}; + static struct mfd_cell tps65090s[] = { { .name = "tps65090-pmic", }, { .name = "tps65090-charger", + .num_resources = ARRAY_SIZE(charger_resources), + .resources = &charger_resources[0], + .of_compatible = "ti,tps65090-charger", }, }; diff --git a/drivers/mfd/twl4030-madc.c b/drivers/mfd/twl4030-madc.c index 942b666a2a07..42bd3ea5df3c 100644 --- a/drivers/mfd/twl4030-madc.c +++ b/drivers/mfd/twl4030-madc.c @@ -211,12 +211,14 @@ static int twl4030battery_current(int raw_volt) * @reg_base - Base address of the first channel * @Channels - 16 bit bitmap. If the bit is set, channel value is read * @buf - The channel values are stored here. if read fails error + * @raw - Return raw values without conversion * value is stored * Returns the number of successfully read channels. */ static int twl4030_madc_read_channels(struct twl4030_madc_data *madc, u8 reg_base, unsigned - long channels, int *buf) + long channels, int *buf, + bool raw) { int count = 0, count_req = 0, i; u8 reg; @@ -230,6 +232,10 @@ static int twl4030_madc_read_channels(struct twl4030_madc_data *madc, count_req++; continue; } + if (raw) { + count++; + continue; + } switch (i) { case 10: buf[i] = twl4030battery_current(buf[i]); @@ -371,7 +377,7 @@ static irqreturn_t twl4030_madc_threaded_irq_handler(int irq, void *_madc) method = &twl4030_conversion_methods[r->method]; /* Read results */ len = twl4030_madc_read_channels(madc, method->rbase, - r->channels, r->rbuf); + r->channels, r->rbuf, r->raw); /* Return results to caller */ if (r->func_cb != NULL) { r->func_cb(len, r->channels, r->rbuf); @@ -397,7 +403,7 @@ err_i2c: method = &twl4030_conversion_methods[r->method]; /* Read results */ len = twl4030_madc_read_channels(madc, method->rbase, - r->channels, r->rbuf); + r->channels, r->rbuf, r->raw); /* Return results to caller */ if (r->func_cb != NULL) { r->func_cb(len, r->channels, r->rbuf); @@ -585,7 +591,7 @@ int twl4030_madc_conversion(struct twl4030_madc_request *req) goto out; } ret = twl4030_madc_read_channels(twl4030_madc, method->rbase, - req->channels, req->rbuf); + req->channels, req->rbuf, req->raw); twl4030_madc->requests[req->method].active = 0; out: diff --git a/drivers/mfd/twl6040.c b/drivers/mfd/twl6040.c index f361bf38a0aa..492ee2cd3400 100644 --- a/drivers/mfd/twl6040.c +++ b/drivers/mfd/twl6040.c @@ -554,7 +554,7 @@ static int twl6040_probe(struct i2c_client *client, twl6040->supplies[0].supply = "vio"; twl6040->supplies[1].supply = "v2v1"; - ret = regulator_bulk_get(&client->dev, TWL6040_NUM_SUPPLIES, + ret = devm_regulator_bulk_get(&client->dev, TWL6040_NUM_SUPPLIES, twl6040->supplies); if (ret != 0) { dev_err(&client->dev, "Failed to get supplies: %d\n", ret); @@ -564,7 +564,7 @@ static int twl6040_probe(struct i2c_client *client, ret = regulator_bulk_enable(TWL6040_NUM_SUPPLIES, twl6040->supplies); if (ret != 0) { dev_err(&client->dev, "Failed to enable supplies: %d\n", ret); - goto power_err; + goto regulator_get_err; } twl6040->dev = &client->dev; @@ -586,8 +586,8 @@ static int twl6040_probe(struct i2c_client *client, twl6040->audpwron = -EINVAL; if (gpio_is_valid(twl6040->audpwron)) { - ret = gpio_request_one(twl6040->audpwron, GPIOF_OUT_INIT_LOW, - "audpwron"); + ret = devm_gpio_request_one(&client->dev, twl6040->audpwron, + GPIOF_OUT_INIT_LOW, "audpwron"); if (ret) goto gpio_err; } @@ -596,14 +596,14 @@ static int twl6040_probe(struct i2c_client *client, IRQF_ONESHOT, 0, &twl6040_irq_chip, &twl6040->irq_data); if (ret < 0) - goto irq_init_err; + goto gpio_err; twl6040->irq_ready = regmap_irq_get_virq(twl6040->irq_data, TWL6040_IRQ_READY); twl6040->irq_th = regmap_irq_get_virq(twl6040->irq_data, TWL6040_IRQ_TH); - ret = request_threaded_irq(twl6040->irq_ready, NULL, + ret = devm_request_threaded_irq(twl6040->dev, twl6040->irq_ready, NULL, twl6040_readyint_handler, IRQF_ONESHOT, "twl6040_irq_ready", twl6040); if (ret) { @@ -611,7 +611,7 @@ static int twl6040_probe(struct i2c_client *client, goto readyirq_err; } - ret = request_threaded_irq(twl6040->irq_th, NULL, + ret = devm_request_threaded_irq(twl6040->dev, twl6040->irq_th, NULL, twl6040_thint_handler, IRQF_ONESHOT, "twl6040_irq_th", twl6040); if (ret) { @@ -681,18 +681,13 @@ static int twl6040_probe(struct i2c_client *client, return 0; mfd_err: - free_irq(twl6040->irq_th, twl6040); + devm_free_irq(&client->dev, twl6040->irq_th, twl6040); thirq_err: - free_irq(twl6040->irq_ready, twl6040); + devm_free_irq(&client->dev, twl6040->irq_ready, twl6040); readyirq_err: regmap_del_irq_chip(twl6040->irq, twl6040->irq_data); -irq_init_err: - if (gpio_is_valid(twl6040->audpwron)) - gpio_free(twl6040->audpwron); gpio_err: regulator_bulk_disable(TWL6040_NUM_SUPPLIES, twl6040->supplies); -power_err: - regulator_bulk_free(TWL6040_NUM_SUPPLIES, twl6040->supplies); regulator_get_err: i2c_set_clientdata(client, NULL); err: @@ -706,18 +701,14 @@ static int twl6040_remove(struct i2c_client *client) if (twl6040->power_count) twl6040_power(twl6040, 0); - if (gpio_is_valid(twl6040->audpwron)) - gpio_free(twl6040->audpwron); - - free_irq(twl6040->irq_ready, twl6040); - free_irq(twl6040->irq_th, twl6040); + devm_free_irq(&client->dev, twl6040->irq_ready, twl6040); + devm_free_irq(&client->dev, twl6040->irq_th, twl6040); regmap_del_irq_chip(twl6040->irq, twl6040->irq_data); mfd_remove_devices(&client->dev); i2c_set_clientdata(client, NULL); regulator_bulk_disable(TWL6040_NUM_SUPPLIES, twl6040->supplies); - regulator_bulk_free(TWL6040_NUM_SUPPLIES, twl6040->supplies); return 0; } diff --git a/drivers/mfd/ucb1400_core.c b/drivers/mfd/ucb1400_core.c index daf69527ed83..e9031fa9d53d 100644 --- a/drivers/mfd/ucb1400_core.c +++ b/drivers/mfd/ucb1400_core.c @@ -75,6 +75,11 @@ static int ucb1400_core_probe(struct device *dev) /* GPIO */ ucb_gpio.ac97 = ac97; + if (pdata) { + ucb_gpio.gpio_setup = pdata->gpio_setup; + ucb_gpio.gpio_teardown = pdata->gpio_teardown; + ucb_gpio.gpio_offset = pdata->gpio_offset; + } ucb->ucb1400_gpio = platform_device_alloc("ucb1400_gpio", -1); if (!ucb->ucb1400_gpio) { err = -ENOMEM; diff --git a/drivers/mfd/vexpress-config.c b/drivers/mfd/vexpress-config.c index 3c1723aa6225..84ce6b9daa3d 100644 --- a/drivers/mfd/vexpress-config.c +++ b/drivers/mfd/vexpress-config.c @@ -184,13 +184,14 @@ static int vexpress_config_schedule(struct vexpress_config_trans *trans) spin_lock_irqsave(&bridge->transactions_lock, flags); - vexpress_config_dump_trans("Executing", trans); - - if (list_empty(&bridge->transactions)) + if (list_empty(&bridge->transactions)) { + vexpress_config_dump_trans("Executing", trans); status = bridge->info->func_exec(trans->func->func, trans->offset, trans->write, trans->data); - else + } else { + vexpress_config_dump_trans("Queuing", trans); status = VEXPRESS_CONFIG_STATUS_WAIT; + } switch (status) { case VEXPRESS_CONFIG_STATUS_DONE: @@ -212,25 +213,31 @@ void vexpress_config_complete(struct vexpress_config_bridge *bridge, { struct vexpress_config_trans *trans; unsigned long flags; + const char *message = "Completed"; spin_lock_irqsave(&bridge->transactions_lock, flags); trans = list_first_entry(&bridge->transactions, struct vexpress_config_trans, list); - vexpress_config_dump_trans("Completed", trans); - trans->status = status; - list_del(&trans->list); - if (!list_empty(&bridge->transactions)) { - vexpress_config_dump_trans("Pending", trans); + do { + vexpress_config_dump_trans(message, trans); + list_del(&trans->list); + complete(&trans->completion); - bridge->info->func_exec(trans->func->func, trans->offset, - trans->write, trans->data); - } - spin_unlock_irqrestore(&bridge->transactions_lock, flags); + if (list_empty(&bridge->transactions)) + break; + + trans = list_first_entry(&bridge->transactions, + struct vexpress_config_trans, list); + vexpress_config_dump_trans("Executing pending", trans); + trans->status = bridge->info->func_exec(trans->func->func, + trans->offset, trans->write, trans->data); + message = "Finished pending"; + } while (trans->status == VEXPRESS_CONFIG_STATUS_DONE); - complete(&trans->completion); + spin_unlock_irqrestore(&bridge->transactions_lock, flags); } EXPORT_SYMBOL(vexpress_config_complete); diff --git a/drivers/mfd/vexpress-sysreg.c b/drivers/mfd/vexpress-sysreg.c index bf75e967a1f3..96a020b1dcd1 100644 --- a/drivers/mfd/vexpress-sysreg.c +++ b/drivers/mfd/vexpress-sysreg.c @@ -490,12 +490,12 @@ static int vexpress_sysreg_probe(struct platform_device *pdev) return err; } + vexpress_sysreg_dev = &pdev->dev; + platform_device_register_data(vexpress_sysreg_dev, "leds-gpio", PLATFORM_DEVID_AUTO, &vexpress_sysreg_leds_pdata, sizeof(vexpress_sysreg_leds_pdata)); - vexpress_sysreg_dev = &pdev->dev; - device_create_file(vexpress_sysreg_dev, &dev_attr_sys_id); return 0; diff --git a/drivers/mfd/wm5102-tables.c b/drivers/mfd/wm5102-tables.c index a433f580aa4c..155c4a1a6a99 100644 --- a/drivers/mfd/wm5102-tables.c +++ b/drivers/mfd/wm5102-tables.c @@ -10,6 +10,7 @@ * published by the Free Software Foundation. */ +#include <linux/device.h> #include <linux/module.h> #include <linux/mfd/arizona/core.h> @@ -57,31 +58,54 @@ static const struct reg_default wm5102_reva_patch[] = { }; static const struct reg_default wm5102_revb_patch[] = { + { 0x19, 0x0001 }, { 0x80, 0x0003 }, { 0x081, 0xE022 }, - { 0x410, 0x4080 }, - { 0x418, 0x4080 }, - { 0x420, 0x4080 }, - { 0x428, 0xC000 }, + { 0x410, 0x6080 }, + { 0x418, 0xa080 }, + { 0x420, 0xa080 }, + { 0x428, 0xe000 }, + { 0x443, 0xDC1A }, { 0x4B0, 0x0066 }, { 0x458, 0x000b }, { 0x212, 0x0000 }, + { 0x171, 0x0000 }, + { 0x35E, 0x000C }, + { 0x2D4, 0x0000 }, { 0x80, 0x0000 }, }; /* We use a function so we can use ARRAY_SIZE() */ int wm5102_patch(struct arizona *arizona) { + const struct reg_default *wm5102_patch; + int ret = 0; + int i, patch_size; + switch (arizona->rev) { case 0: - return regmap_register_patch(arizona->regmap, - wm5102_reva_patch, - ARRAY_SIZE(wm5102_reva_patch)); + wm5102_patch = wm5102_reva_patch; + patch_size = ARRAY_SIZE(wm5102_reva_patch); default: - return regmap_register_patch(arizona->regmap, - wm5102_revb_patch, - ARRAY_SIZE(wm5102_revb_patch)); + wm5102_patch = wm5102_revb_patch; + patch_size = ARRAY_SIZE(wm5102_revb_patch); + } + + regcache_cache_bypass(arizona->regmap, true); + + for (i = 0; i < patch_size; i++) { + ret = regmap_write(arizona->regmap, wm5102_patch[i].reg, + wm5102_patch[i].def); + if (ret != 0) { + dev_err(arizona->dev, "Failed to write %x = %x: %d\n", + wm5102_patch[i].reg, wm5102_patch[i].def, ret); + goto out; + } } + +out: + regcache_cache_bypass(arizona->regmap, false); + return ret; } static const struct regmap_irq wm5102_aod_irqs[ARIZONA_NUM_IRQ] = { @@ -282,7 +306,7 @@ static const struct reg_default wm5102_reg_default[] = { { 0x00000155, 0x0000 }, /* R341 - Rate Estimator 4 */ { 0x00000156, 0x0000 }, /* R342 - Rate Estimator 5 */ { 0x00000161, 0x0000 }, /* R353 - Dynamic Frequency Scaling 1 */ - { 0x00000171, 0x0002 }, /* R369 - FLL1 Control 1 */ + { 0x00000171, 0x0000 }, /* R369 - FLL1 Control 1 */ { 0x00000172, 0x0008 }, /* R370 - FLL1 Control 2 */ { 0x00000173, 0x0018 }, /* R371 - FLL1 Control 3 */ { 0x00000174, 0x007D }, /* R372 - FLL1 Control 4 */ @@ -290,12 +314,14 @@ static const struct reg_default wm5102_reg_default[] = { { 0x00000176, 0x0000 }, /* R374 - FLL1 Control 6 */ { 0x00000177, 0x0181 }, /* R375 - FLL1 Loop Filter Test 1 */ { 0x00000178, 0x0000 }, /* R376 - FLL1 NCO Test 0 */ + { 0x00000179, 0x0000 }, /* R377 - FLL1 Control 7 */ { 0x00000181, 0x0000 }, /* R385 - FLL1 Synchroniser 1 */ { 0x00000182, 0x0000 }, /* R386 - FLL1 Synchroniser 2 */ { 0x00000183, 0x0000 }, /* R387 - FLL1 Synchroniser 3 */ { 0x00000184, 0x0000 }, /* R388 - FLL1 Synchroniser 4 */ { 0x00000185, 0x0000 }, /* R389 - FLL1 Synchroniser 5 */ { 0x00000186, 0x0000 }, /* R390 - FLL1 Synchroniser 6 */ + { 0x00000187, 0x0001 }, /* R391 - FLL1 Synchroniser 7 */ { 0x00000189, 0x0000 }, /* R393 - FLL1 Spread Spectrum */ { 0x0000018A, 0x0004 }, /* R394 - FLL1 GPIO Clock */ { 0x00000191, 0x0000 }, /* R401 - FLL2 Control 1 */ @@ -306,12 +332,14 @@ static const struct reg_default wm5102_reg_default[] = { { 0x00000196, 0x0000 }, /* R406 - FLL2 Control 6 */ { 0x00000197, 0x0000 }, /* R407 - FLL2 Loop Filter Test 1 */ { 0x00000198, 0x0000 }, /* R408 - FLL2 NCO Test 0 */ + { 0x00000199, 0x0000 }, /* R409 - FLL2 Control 7 */ { 0x000001A1, 0x0000 }, /* R417 - FLL2 Synchroniser 1 */ { 0x000001A2, 0x0000 }, /* R418 - FLL2 Synchroniser 2 */ { 0x000001A3, 0x0000 }, /* R419 - FLL2 Synchroniser 3 */ { 0x000001A4, 0x0000 }, /* R420 - FLL2 Synchroniser 4 */ { 0x000001A5, 0x0000 }, /* R421 - FLL2 Synchroniser 5 */ { 0x000001A6, 0x0000 }, /* R422 - FLL2 Synchroniser 6 */ + { 0x000001A7, 0x0001 }, /* R423 - FLL2 Synchroniser 7 */ { 0x000001A9, 0x0000 }, /* R425 - FLL2 Spread Spectrum */ { 0x000001AA, 0x0004 }, /* R426 - FLL2 GPIO Clock */ { 0x00000200, 0x0006 }, /* R512 - Mic Charge Pump 1 */ @@ -331,6 +359,10 @@ static const struct reg_default wm5102_reg_default[] = { { 0x000002A3, 0x1102 }, /* R675 - Mic Detect 1 */ { 0x000002A4, 0x009F }, /* R676 - Mic Detect 2 */ { 0x000002A5, 0x0000 }, /* R677 - Mic Detect 3 */ + { 0x000002A6, 0x3737 }, /* R678 - Mic Detect Level 1 */ + { 0x000002A7, 0x372C }, /* R679 - Mic Detect Level 2 */ + { 0x000002A8, 0x1422 }, /* R680 - Mic Detect Level 3 */ + { 0x000002A9, 0x030A }, /* R681 - Mic Detect Level 4 */ { 0x000002C3, 0x0000 }, /* R707 - Mic noise mix control 1 */ { 0x000002CB, 0x0000 }, /* R715 - Isolation control */ { 0x000002D3, 0x0000 }, /* R723 - Jack detect analogue */ @@ -358,7 +390,7 @@ static const struct reg_default wm5102_reg_default[] = { { 0x00000400, 0x0000 }, /* R1024 - Output Enables 1 */ { 0x00000408, 0x0000 }, /* R1032 - Output Rate 1 */ { 0x00000409, 0x0022 }, /* R1033 - Output Volume Ramp */ - { 0x00000410, 0x4080 }, /* R1040 - Output Path Config 1L */ + { 0x00000410, 0x6080 }, /* R1040 - Output Path Config 1L */ { 0x00000411, 0x0180 }, /* R1041 - DAC Digital Volume 1L */ { 0x00000412, 0x0081 }, /* R1042 - DAC Volume Limit 1L */ { 0x00000413, 0x0001 }, /* R1043 - Noise Gate Select 1L */ @@ -366,7 +398,7 @@ static const struct reg_default wm5102_reg_default[] = { { 0x00000415, 0x0180 }, /* R1045 - DAC Digital Volume 1R */ { 0x00000416, 0x0081 }, /* R1046 - DAC Volume Limit 1R */ { 0x00000417, 0x0002 }, /* R1047 - Noise Gate Select 1R */ - { 0x00000418, 0x4080 }, /* R1048 - Output Path Config 2L */ + { 0x00000418, 0xA080 }, /* R1048 - Output Path Config 2L */ { 0x00000419, 0x0180 }, /* R1049 - DAC Digital Volume 2L */ { 0x0000041A, 0x0081 }, /* R1050 - DAC Volume Limit 2L */ { 0x0000041B, 0x0004 }, /* R1051 - Noise Gate Select 2L */ @@ -374,11 +406,11 @@ static const struct reg_default wm5102_reg_default[] = { { 0x0000041D, 0x0180 }, /* R1053 - DAC Digital Volume 2R */ { 0x0000041E, 0x0081 }, /* R1054 - DAC Volume Limit 2R */ { 0x0000041F, 0x0008 }, /* R1055 - Noise Gate Select 2R */ - { 0x00000420, 0x4080 }, /* R1056 - Output Path Config 3L */ + { 0x00000420, 0xA080 }, /* R1056 - Output Path Config 3L */ { 0x00000421, 0x0180 }, /* R1057 - DAC Digital Volume 3L */ { 0x00000422, 0x0081 }, /* R1058 - DAC Volume Limit 3L */ { 0x00000423, 0x0010 }, /* R1059 - Noise Gate Select 3L */ - { 0x00000428, 0xC000 }, /* R1064 - Output Path Config 4L */ + { 0x00000428, 0xE000 }, /* R1064 - Output Path Config 4L */ { 0x00000429, 0x0180 }, /* R1065 - DAC Digital Volume 4L */ { 0x0000042A, 0x0081 }, /* R1066 - Out Volume 4L */ { 0x0000042B, 0x0040 }, /* R1067 - Noise Gate Select 4L */ @@ -393,7 +425,7 @@ static const struct reg_default wm5102_reg_default[] = { { 0x00000436, 0x0081 }, /* R1078 - DAC Volume Limit 5R */ { 0x00000437, 0x0200 }, /* R1079 - Noise Gate Select 5R */ { 0x00000450, 0x0000 }, /* R1104 - DAC AEC Control 1 */ - { 0x00000458, 0x0001 }, /* R1112 - Noise Gate Control */ + { 0x00000458, 0x000B }, /* R1112 - Noise Gate Control */ { 0x00000490, 0x0069 }, /* R1168 - PDM SPK1 CTRL 1 */ { 0x00000491, 0x0000 }, /* R1169 - PDM SPK1 CTRL 2 */ { 0x00000500, 0x000C }, /* R1280 - AIF1 BCLK Ctrl */ @@ -1051,12 +1083,14 @@ static bool wm5102_readable_register(struct device *dev, unsigned int reg) case ARIZONA_FLL1_CONTROL_6: case ARIZONA_FLL1_LOOP_FILTER_TEST_1: case ARIZONA_FLL1_NCO_TEST_0: + case ARIZONA_FLL1_CONTROL_7: case ARIZONA_FLL1_SYNCHRONISER_1: case ARIZONA_FLL1_SYNCHRONISER_2: case ARIZONA_FLL1_SYNCHRONISER_3: case ARIZONA_FLL1_SYNCHRONISER_4: case ARIZONA_FLL1_SYNCHRONISER_5: case ARIZONA_FLL1_SYNCHRONISER_6: + case ARIZONA_FLL1_SYNCHRONISER_7: case ARIZONA_FLL1_SPREAD_SPECTRUM: case ARIZONA_FLL1_GPIO_CLOCK: case ARIZONA_FLL2_CONTROL_1: @@ -1067,12 +1101,14 @@ static bool wm5102_readable_register(struct device *dev, unsigned int reg) case ARIZONA_FLL2_CONTROL_6: case ARIZONA_FLL2_LOOP_FILTER_TEST_1: case ARIZONA_FLL2_NCO_TEST_0: + case ARIZONA_FLL2_CONTROL_7: case ARIZONA_FLL2_SYNCHRONISER_1: case ARIZONA_FLL2_SYNCHRONISER_2: case ARIZONA_FLL2_SYNCHRONISER_3: case ARIZONA_FLL2_SYNCHRONISER_4: case ARIZONA_FLL2_SYNCHRONISER_5: case ARIZONA_FLL2_SYNCHRONISER_6: + case ARIZONA_FLL2_SYNCHRONISER_7: case ARIZONA_FLL2_SPREAD_SPECTRUM: case ARIZONA_FLL2_GPIO_CLOCK: case ARIZONA_MIC_CHARGE_PUMP_1: @@ -1090,6 +1126,10 @@ static bool wm5102_readable_register(struct device *dev, unsigned int reg) case ARIZONA_MIC_DETECT_1: case ARIZONA_MIC_DETECT_2: case ARIZONA_MIC_DETECT_3: + case ARIZONA_MIC_DETECT_LEVEL_1: + case ARIZONA_MIC_DETECT_LEVEL_2: + case ARIZONA_MIC_DETECT_LEVEL_3: + case ARIZONA_MIC_DETECT_LEVEL_4: case ARIZONA_MIC_NOISE_MIX_CONTROL_1: case ARIZONA_ISOLATION_CONTROL: case ARIZONA_JACK_DETECT_ANALOGUE: @@ -1161,6 +1201,8 @@ static bool wm5102_readable_register(struct device *dev, unsigned int reg) case ARIZONA_NOISE_GATE_CONTROL: case ARIZONA_PDM_SPK1_CTRL_1: case ARIZONA_PDM_SPK1_CTRL_2: + case ARIZONA_SPK_CTRL_2: + case ARIZONA_SPK_CTRL_3: case ARIZONA_DAC_COMP_1: case ARIZONA_DAC_COMP_2: case ARIZONA_DAC_COMP_3: diff --git a/drivers/mfd/wm831x-spi.c b/drivers/mfd/wm831x-spi.c index 4e70e157a909..e7ed14f661d8 100644 --- a/drivers/mfd/wm831x-spi.c +++ b/drivers/mfd/wm831x-spi.c @@ -37,7 +37,7 @@ static int wm831x_spi_probe(struct spi_device *spi) spi->bits_per_word = 16; spi->mode = SPI_MODE_0; - dev_set_drvdata(&spi->dev, wm831x); + spi_set_drvdata(spi, wm831x); wm831x->dev = &spi->dev; wm831x->regmap = devm_regmap_init_spi(spi, &wm831x_regmap_config); @@ -53,7 +53,7 @@ static int wm831x_spi_probe(struct spi_device *spi) static int wm831x_spi_remove(struct spi_device *spi) { - struct wm831x *wm831x = dev_get_drvdata(&spi->dev); + struct wm831x *wm831x = spi_get_drvdata(spi); wm831x_device_exit(wm831x); @@ -69,7 +69,7 @@ static int wm831x_spi_suspend(struct device *dev) static void wm831x_spi_shutdown(struct spi_device *spi) { - struct wm831x *wm831x = dev_get_drvdata(&spi->dev); + struct wm831x *wm831x = spi_get_drvdata(spi); wm831x_device_shutdown(wm831x); } diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index 803e93fae56a..00e4fe2f3c75 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -19,6 +19,9 @@ #include <linux/err.h> #include <linux/delay.h> #include <linux/mfd/core.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> @@ -191,7 +194,7 @@ static const char *wm8958_main_supplies[] = { "SPKVDD2", }; -#ifdef CONFIG_PM +#ifdef CONFIG_PM_RUNTIME static int wm8994_suspend(struct device *dev) { struct wm8994 *wm8994 = dev_get_drvdata(dev); @@ -396,6 +399,60 @@ static const struct reg_default wm1811_reva_patch[] = { { 0x102, 0x0 }, }; +#ifdef CONFIG_OF +static int wm8994_set_pdata_from_of(struct wm8994 *wm8994) +{ + struct device_node *np = wm8994->dev->of_node; + struct wm8994_pdata *pdata = &wm8994->pdata; + int i; + + if (!np) + return 0; + + if (of_property_read_u32_array(np, "wlf,gpio-cfg", pdata->gpio_defaults, + ARRAY_SIZE(pdata->gpio_defaults)) >= 0) { + for (i = 0; i < ARRAY_SIZE(pdata->gpio_defaults); i++) { + if (wm8994->pdata.gpio_defaults[i] == 0) + pdata->gpio_defaults[i] + = WM8994_CONFIGURE_GPIO; + } + } + + of_property_read_u32_array(np, "wlf,micbias-cfg", pdata->micbias, + ARRAY_SIZE(pdata->micbias)); + + pdata->lineout1_diff = true; + pdata->lineout2_diff = true; + if (of_find_property(np, "wlf,lineout1-se", NULL)) + pdata->lineout1_diff = false; + if (of_find_property(np, "wlf,lineout2-se", NULL)) + pdata->lineout2_diff = false; + + if (of_find_property(np, "wlf,lineout1-feedback", NULL)) + pdata->lineout1fb = true; + if (of_find_property(np, "wlf,lineout2-feedback", NULL)) + pdata->lineout2fb = true; + + if (of_find_property(np, "wlf,ldoena-always-driven", NULL)) + pdata->lineout2fb = true; + + pdata->ldo[0].enable = of_get_named_gpio(np, "wlf,ldo1ena", 0); + if (pdata->ldo[0].enable < 0) + pdata->ldo[0].enable = 0; + + pdata->ldo[1].enable = of_get_named_gpio(np, "wlf,ldo2ena", 0); + if (pdata->ldo[1].enable < 0) + pdata->ldo[1].enable = 0; + + return 0; +} +#else +static int wm8994_set_pdata_from_of(struct wm8994 *wm8994) +{ + return 0; +} +#endif + /* * Instantiate the generic non-control parts of the device. */ @@ -405,7 +462,7 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) struct regmap_config *regmap_config; const struct reg_default *regmap_patch = NULL; const char *devname; - int ret, i, patch_regs; + int ret, i, patch_regs = 0; int pulls = 0; if (dev_get_platdata(wm8994->dev)) { @@ -414,6 +471,10 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) } pdata = &wm8994->pdata; + ret = wm8994_set_pdata_from_of(wm8994); + if (ret != 0) + return ret; + dev_set_drvdata(wm8994->dev, wm8994); /* Add the on-chip regulators first for bootstrapping */ @@ -673,9 +734,9 @@ static void wm8994_device_exit(struct wm8994 *wm8994) } static const struct of_device_id wm8994_of_match[] = { - { .compatible = "wlf,wm1811", }, - { .compatible = "wlf,wm8994", }, - { .compatible = "wlf,wm8958", }, + { .compatible = "wlf,wm1811", .data = (void *)WM1811 }, + { .compatible = "wlf,wm8994", .data = (void *)WM8994 }, + { .compatible = "wlf,wm8958", .data = (void *)WM8958 }, { } }; MODULE_DEVICE_TABLE(of, wm8994_of_match); @@ -683,6 +744,7 @@ MODULE_DEVICE_TABLE(of, wm8994_of_match); static int wm8994_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { + const struct of_device_id *of_id; struct wm8994 *wm8994; int ret; @@ -693,7 +755,14 @@ static int wm8994_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, wm8994); wm8994->dev = &i2c->dev; wm8994->irq = i2c->irq; - wm8994->type = id->driver_data; + + if (i2c->dev.of_node) { + of_id = of_match_device(wm8994_of_match, &i2c->dev); + if (of_id) + wm8994->type = (int)of_id->data; + } else { + wm8994->type = id->driver_data; + } wm8994->regmap = devm_regmap_init_i2c(i2c, &wm8994_base_regmap_config); if (IS_ERR(wm8994->regmap)) { @@ -724,15 +793,16 @@ static const struct i2c_device_id wm8994_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, wm8994_i2c_id); -static UNIVERSAL_DEV_PM_OPS(wm8994_pm_ops, wm8994_suspend, wm8994_resume, - NULL); +static const struct dev_pm_ops wm8994_pm_ops = { + SET_RUNTIME_PM_OPS(wm8994_suspend, wm8994_resume, NULL) +}; static struct i2c_driver wm8994_i2c_driver = { .driver = { .name = "wm8994", .owner = THIS_MODULE, .pm = &wm8994_pm_ops, - .of_match_table = wm8994_of_match, + .of_match_table = of_match_ptr(wm8994_of_match), }, .probe = wm8994_i2c_probe, .remove = wm8994_i2c_remove, |