blob: 54d0fe40845f0f00bf895d6edce27ea422eabd2e [file] [log] [blame]
/*
* Copyright (C) 2007-2011 ST-Ericsson
* License terms: GNU General Public License (GPL) version 2
* Low-level core for exclusive access to the AB5500 IC on the I2C bus
* and some basic chip-configuration.
* Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com>
* Author: Mattias Nilsson <mattias.i.nilsson@stericsson.com>
* Author: Mattias Wallin <mattias.wallin@stericsson.com>
* Author: Rickard Andersson <rickard.andersson@stericsson.com>
* Author: Karl Komierowski <karl.komierowski@stericsson.com>
* Author: Bibek Basu <bibek.basu@stericsson.com>
*
* TODO: Event handling with irq_chip. Waiting for PRCMU fw support.
*/
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/random.h>
#include <linux/mfd/abx500.h>
#include <linux/mfd/abx500/ab5500.h>
#include <linux/list.h>
#include <linux/bitops.h>
#include <linux/spinlock.h>
#include <linux/mfd/core.h>
#include <linux/mfd/db5500-prcmu.h>
#include "ab5500-core.h"
#include "ab5500-debugfs.h"
#define AB5500_NUM_EVENT_REG 23
#define AB5500_IT_LATCH0_REG 0x40
#define AB5500_IT_MASK0_REG 0x60
/*
* Permissible register ranges for reading and writing per device and bank.
*
* The ranges must be listed in increasing address order, and no overlaps are
* allowed. It is assumed that write permission implies read permission
* (i.e. only RO and RW permissions should be used). Ranges with write
* permission must not be split up.
*/
#define NO_RANGE {.count = 0, .range = NULL,}
static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = {
[AB5500_DEVID_USB] = {
.nbanks = 1,
.bank = (struct ab5500_i2c_ranges []) {
{
.bankid = AB5500_BANK_USB,
.nranges = 12,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x01,
.last = 0x01,
.perm = AB5500_PERM_RW,
},
{
.first = 0x80,
.last = 0x83,
.perm = AB5500_PERM_RW,
},
{
.first = 0x87,
.last = 0x8A,
.perm = AB5500_PERM_RW,
},
{
.first = 0x8B,
.last = 0x8B,
.perm = AB5500_PERM_RO,
},
{
.first = 0x91,
.last = 0x92,
.perm = AB5500_PERM_RO,
},
{
.first = 0x93,
.last = 0x93,
.perm = AB5500_PERM_RW,
},
{
.first = 0x94,
.last = 0x94,
.perm = AB5500_PERM_RO,
},
{
.first = 0xA8,
.last = 0xB0,
.perm = AB5500_PERM_RO,
},
{
.first = 0xB2,
.last = 0xB2,
.perm = AB5500_PERM_RO,
},
{
.first = 0xB4,
.last = 0xBC,
.perm = AB5500_PERM_RO,
},
{
.first = 0xBF,
.last = 0xBF,
.perm = AB5500_PERM_RO,
},
{
.first = 0xC1,
.last = 0xC5,
.perm = AB5500_PERM_RO,
},
},
},
},
},
[AB5500_DEVID_ADC] = {
.nbanks = 1,
.bank = (struct ab5500_i2c_ranges []) {
{
.bankid = AB5500_BANK_ADC,
.nranges = 6,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x1F,
.last = 0x22,
.perm = AB5500_PERM_RO,
},
{
.first = 0x23,
.last = 0x24,
.perm = AB5500_PERM_RW,
},
{
.first = 0x26,
.last = 0x2D,
.perm = AB5500_PERM_RO,
},
{
.first = 0x2F,
.last = 0x34,
.perm = AB5500_PERM_RW,
},
{
.first = 0x37,
.last = 0x57,
.perm = AB5500_PERM_RW,
},
{
.first = 0x58,
.last = 0x58,
.perm = AB5500_PERM_RO,
},
},
},
},
},
[AB5500_DEVID_LEDS] = {
.nbanks = 1,
.bank = (struct ab5500_i2c_ranges []) {
{
.bankid = AB5500_BANK_LED,
.nranges = 1,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x00,
.last = 0x0C,
.perm = AB5500_PERM_RW,
},
},
},
},
},
[AB5500_DEVID_VIDEO] = {
.nbanks = 1,
.bank = (struct ab5500_i2c_ranges []) {
{
.bankid = AB5500_BANK_VDENC,
.nranges = 12,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x00,
.last = 0x08,
.perm = AB5500_PERM_RW,
},
{
.first = 0x09,
.last = 0x09,
.perm = AB5500_PERM_RO,
},
{
.first = 0x0A,
.last = 0x12,
.perm = AB5500_PERM_RW,
},
{
.first = 0x15,
.last = 0x19,
.perm = AB5500_PERM_RW,
},
{
.first = 0x1B,
.last = 0x21,
.perm = AB5500_PERM_RW,
},
{
.first = 0x27,
.last = 0x2C,
.perm = AB5500_PERM_RW,
},
{
.first = 0x41,
.last = 0x41,
.perm = AB5500_PERM_RW,
},
{
.first = 0x45,
.last = 0x5B,
.perm = AB5500_PERM_RW,
},
{
.first = 0x5D,
.last = 0x5D,
.perm = AB5500_PERM_RW,
},
{
.first = 0x69,
.last = 0x69,
.perm = AB5500_PERM_RW,
},
{
.first = 0x6C,
.last = 0x6D,
.perm = AB5500_PERM_RW,
},
{
.first = 0x80,
.last = 0x81,
.perm = AB5500_PERM_RW,
},
},
},
},
},
[AB5500_DEVID_REGULATORS] = {
.nbanks = 2,
.bank = (struct ab5500_i2c_ranges []) {
{
.bankid = AB5500_BANK_STARTUP,
.nranges = 12,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x00,
.last = 0x01,
.perm = AB5500_PERM_RW,
},
{
.first = 0x1F,
.last = 0x1F,
.perm = AB5500_PERM_RW,
},
{
.first = 0x2E,
.last = 0x2E,
.perm = AB5500_PERM_RO,
},
{
.first = 0x2F,
.last = 0x30,
.perm = AB5500_PERM_RW,
},
{
.first = 0x50,
.last = 0x51,
.perm = AB5500_PERM_RW,
},
{
.first = 0x60,
.last = 0x61,
.perm = AB5500_PERM_RW,
},
{
.first = 0x66,
.last = 0x8A,
.perm = AB5500_PERM_RW,
},
{
.first = 0x8C,
.last = 0x96,
.perm = AB5500_PERM_RW,
},
{
.first = 0xAA,
.last = 0xB4,
.perm = AB5500_PERM_RW,
},
{
.first = 0xB7,
.last = 0xBF,
.perm = AB5500_PERM_RW,
},
{
.first = 0xC1,
.last = 0xCA,
.perm = AB5500_PERM_RW,
},
{
.first = 0xD3,
.last = 0xE0,
.perm = AB5500_PERM_RW,
},
},
},
{
.bankid = AB5500_BANK_SIM_USBSIM,
.nranges = 1,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x13,
.last = 0x19,
.perm = AB5500_PERM_RW,
},
},
},
},
},
[AB5500_DEVID_SIM] = {
.nbanks = 1,
.bank = (struct ab5500_i2c_ranges []) {
{
.bankid = AB5500_BANK_SIM_USBSIM,
.nranges = 1,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x13,
.last = 0x19,
.perm = AB5500_PERM_RW,
},
},
},
},
},
[AB5500_DEVID_RTC] = {
.nbanks = 1,
.bank = (struct ab5500_i2c_ranges []) {
{
.bankid = AB5500_BANK_RTC,
.nranges = 2,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x00,
.last = 0x04,
.perm = AB5500_PERM_RW,
},
{
.first = 0x06,
.last = 0x0C,
.perm = AB5500_PERM_RW,
},
},
},
},
},
[AB5500_DEVID_CHARGER] = {
.nbanks = 1,
.bank = (struct ab5500_i2c_ranges []) {
{
.bankid = AB5500_BANK_CHG,
.nranges = 2,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x11,
.last = 0x11,
.perm = AB5500_PERM_RO,
},
{
.first = 0x12,
.last = 0x1B,
.perm = AB5500_PERM_RW,
},
},
},
},
},
[AB5500_DEVID_FUELGAUGE] = {
.nbanks = 1,
.bank = (struct ab5500_i2c_ranges []) {
{
.bankid = AB5500_BANK_FG_BATTCOM_ACC,
.nranges = 2,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x00,
.last = 0x0B,
.perm = AB5500_PERM_RO,
},
{
.first = 0x0C,
.last = 0x10,
.perm = AB5500_PERM_RW,
},
},
},
},
},
[AB5500_DEVID_VIBRATOR] = {
.nbanks = 1,
.bank = (struct ab5500_i2c_ranges []) {
{
.bankid = AB5500_BANK_VIBRA,
.nranges = 2,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x10,
.last = 0x13,
.perm = AB5500_PERM_RW,
},
{
.first = 0xFE,
.last = 0xFE,
.perm = AB5500_PERM_RW,
},
},
},
},
},
[AB5500_DEVID_CODEC] = {
.nbanks = 1,
.bank = (struct ab5500_i2c_ranges []) {
{
.bankid = AB5500_BANK_AUDIO_HEADSETUSB,
.nranges = 2,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x00,
.last = 0x48,
.perm = AB5500_PERM_RW,
},
{
.first = 0xEB,
.last = 0xFB,
.perm = AB5500_PERM_RW,
},
},
},
},
},
[AB5500_DEVID_POWER] = {
.nbanks = 2,
.bank = (struct ab5500_i2c_ranges []) {
{
.bankid = AB5500_BANK_STARTUP,
.nranges = 1,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x30,
.last = 0x30,
.perm = AB5500_PERM_RW,
},
},
},
{
.bankid = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP,
.nranges = 1,
.range = (struct ab5500_reg_range[]) {
{
.first = 0x01,
.last = 0x01,
.perm = AB5500_PERM_RW,
},
},
},
},
},
};
#define AB5500_IRQ(bank, bit) ((bank) * 8 + (bit))
/* I appologize for the resource names beeing a mix of upper case
* and lower case but I want them to be exact as the documentation */
static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = {
[AB5500_DEVID_LEDS] = {
.name = "ab5500-leds",
.id = AB5500_DEVID_LEDS,
},
[AB5500_DEVID_POWER] = {
.name = "ab5500-power",
.id = AB5500_DEVID_POWER,
},
[AB5500_DEVID_REGULATORS] = {
.name = "ab5500-regulator",
.id = AB5500_DEVID_REGULATORS,
},
[AB5500_DEVID_SIM] = {
.name = "ab5500-sim",
.id = AB5500_DEVID_SIM,
.num_resources = 1,
.resources = (struct resource[]) {
{
.name = "SIMOFF",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(2, 0), /*rising*/
.end = AB5500_IRQ(2, 1), /*falling*/
},
},
},
[AB5500_DEVID_RTC] = {
.name = "ab5500-rtc",
.id = AB5500_DEVID_RTC,
.num_resources = 1,
.resources = (struct resource[]) {
{
.name = "RTC_Alarm",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(1, 7),
.end = AB5500_IRQ(1, 7),
}
},
},
[AB5500_DEVID_CHARGER] = {
.name = "ab5500-charger",
.id = AB5500_DEVID_CHARGER,
},
[AB5500_DEVID_ADC] = {
.name = "ab5500-adc",
.id = AB5500_DEVID_ADC,
.num_resources = 10,
.resources = (struct resource[]) {
{
.name = "TRIGGER-0",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(0, 0),
.end = AB5500_IRQ(0, 0),
},
{
.name = "TRIGGER-1",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(0, 1),
.end = AB5500_IRQ(0, 1),
},
{
.name = "TRIGGER-2",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(0, 2),
.end = AB5500_IRQ(0, 2),
},
{
.name = "TRIGGER-3",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(0, 3),
.end = AB5500_IRQ(0, 3),
},
{
.name = "TRIGGER-4",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(0, 4),
.end = AB5500_IRQ(0, 4),
},
{
.name = "TRIGGER-5",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(0, 5),
.end = AB5500_IRQ(0, 5),
},
{
.name = "TRIGGER-6",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(0, 6),
.end = AB5500_IRQ(0, 6),
},
{
.name = "TRIGGER-7",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(0, 7),
.end = AB5500_IRQ(0, 7),
},
{
.name = "TRIGGER-VBAT",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(0, 8),
.end = AB5500_IRQ(0, 8),
},
{
.name = "TRIGGER-VBAT-TXON",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(0, 9),
.end = AB5500_IRQ(0, 9),
},
},
},
[AB5500_DEVID_FUELGAUGE] = {
.name = "ab5500-fuelgauge",
.id = AB5500_DEVID_FUELGAUGE,
.num_resources = 6,
.resources = (struct resource[]) {
{
.name = "Batt_attach",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(7, 5),
.end = AB5500_IRQ(7, 5),
},
{
.name = "Batt_removal",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(7, 6),
.end = AB5500_IRQ(7, 6),
},
{
.name = "UART_framing",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(7, 7),
.end = AB5500_IRQ(7, 7),
},
{
.name = "UART_overrun",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(8, 0),
.end = AB5500_IRQ(8, 0),
},
{
.name = "UART_Rdy_RX",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(8, 1),
.end = AB5500_IRQ(8, 1),
},
{
.name = "UART_Rdy_TX",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(8, 2),
.end = AB5500_IRQ(8, 2),
},
},
},
[AB5500_DEVID_VIBRATOR] = {
.name = "ab5500-vibrator",
.id = AB5500_DEVID_VIBRATOR,
},
[AB5500_DEVID_CODEC] = {
.name = "ab5500-codec",
.id = AB5500_DEVID_CODEC,
.num_resources = 3,
.resources = (struct resource[]) {
{
.name = "audio_spkr1_ovc",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(9, 5),
.end = AB5500_IRQ(9, 5),
},
{
.name = "audio_plllocked",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(9, 6),
.end = AB5500_IRQ(9, 6),
},
{
.name = "audio_spkr2_ovc",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(17, 4),
.end = AB5500_IRQ(17, 4),
},
},
},
[AB5500_DEVID_USB] = {
.name = "ab5500-usb",
.id = AB5500_DEVID_USB,
.num_resources = 36,
.resources = (struct resource[]) {
{
.name = "Link_Update",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(22, 1),
.end = AB5500_IRQ(22, 1),
},
{
.name = "DCIO",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(8, 3),
.end = AB5500_IRQ(8, 4),
},
{
.name = "VBUS_R",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(8, 5),
.end = AB5500_IRQ(8, 5),
},
{
.name = "VBUS_F",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(8, 6),
.end = AB5500_IRQ(8, 6),
},
{
.name = "CHGstate_10_PCVBUSchg",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(8, 7),
.end = AB5500_IRQ(8, 7),
},
{
.name = "DCIOreverse_ovc",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(9, 0),
.end = AB5500_IRQ(9, 0),
},
{
.name = "USBCharDetDone",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(9, 1),
.end = AB5500_IRQ(9, 1),
},
{
.name = "DCIO_no_limit",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(9, 2),
.end = AB5500_IRQ(9, 2),
},
{
.name = "USB_suspend",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(9, 3),
.end = AB5500_IRQ(9, 3),
},
{
.name = "DCIOreverse_fwdcurrent",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(9, 4),
.end = AB5500_IRQ(9, 4),
},
{
.name = "Vbus_Imeasmax_change",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(9, 5),
.end = AB5500_IRQ(9, 6),
},
{
.name = "OVV",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(14, 5),
.end = AB5500_IRQ(14, 5),
},
{
.name = "USBcharging_NOTok",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(15, 3),
.end = AB5500_IRQ(15, 3),
},
{
.name = "usb_adp_sensoroff",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(15, 6),
.end = AB5500_IRQ(15, 6),
},
{
.name = "usb_adp_probeplug",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(15, 7),
.end = AB5500_IRQ(15, 7),
},
{
.name = "usb_adp_sinkerror",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(16, 0),
.end = AB5500_IRQ(16, 6),
},
{
.name = "usb_adp_sourceerror",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(16, 1),
.end = AB5500_IRQ(16, 1),
},
{
.name = "usb_idgnd_r",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(16, 2),
.end = AB5500_IRQ(16, 2),
},
{
.name = "usb_idgnd_f",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(16, 3),
.end = AB5500_IRQ(16, 3),
},
{
.name = "usb_iddetR1",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(16, 4),
.end = AB5500_IRQ(16, 5),
},
{
.name = "usb_iddetR2",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(16, 6),
.end = AB5500_IRQ(16, 7),
},
{
.name = "usb_iddetR3",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(17, 0),
.end = AB5500_IRQ(17, 1),
},
{
.name = "usb_iddetR4",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(17, 2),
.end = AB5500_IRQ(17, 3),
},
{
.name = "CharTempWindowOk",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(17, 7),
.end = AB5500_IRQ(18, 0),
},
{
.name = "USB_SprDetect",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(18, 1),
.end = AB5500_IRQ(18, 1),
},
{
.name = "usb_adp_probe_unplug",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(18, 2),
.end = AB5500_IRQ(18, 2),
},
{
.name = "VBUSChDrop",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(18, 3),
.end = AB5500_IRQ(18, 4),
},
{
.name = "dcio_char_rec_done",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(18, 5),
.end = AB5500_IRQ(18, 5),
},
{
.name = "Charging_stopped_by_temp",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(18, 6),
.end = AB5500_IRQ(18, 6),
},
{
.name = "CHGstate_11_SafeModeVBUS",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(21, 1),
.end = AB5500_IRQ(21, 2),
},
{
.name = "CHGstate_12_comletedVBUS",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(21, 2),
.end = AB5500_IRQ(21, 2),
},
{
.name = "CHGstate_13_completedVBUS",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(21, 3),
.end = AB5500_IRQ(21, 3),
},
{
.name = "CHGstate_14_FullChgDCIO",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(21, 4),
.end = AB5500_IRQ(21, 4),
},
{
.name = "CHGstate_15_SafeModeDCIO",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(21, 5),
.end = AB5500_IRQ(21, 5),
},
{
.name = "CHGstate_16_OFFsuspendDCIO",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(21, 6),
.end = AB5500_IRQ(21, 6),
},
{
.name = "CHGstate_17_completedDCIO",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(21, 7),
.end = AB5500_IRQ(21, 7),
},
},
},
[AB5500_DEVID_OTP] = {
.name = "ab5500-otp",
.id = AB5500_DEVID_OTP,
},
[AB5500_DEVID_VIDEO] = {
.name = "ab5500-video",
.id = AB5500_DEVID_VIDEO,
.num_resources = 1,
.resources = (struct resource[]) {
{
.name = "plugTVdet",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(22, 2),
.end = AB5500_IRQ(22, 2),
},
},
},
[AB5500_DEVID_DBIECI] = {
.name = "ab5500-dbieci",
.id = AB5500_DEVID_DBIECI,
.num_resources = 10,
.resources = (struct resource[]) {
{
.name = "COLL",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(14, 0),
.end = AB5500_IRQ(14, 0),
},
{
.name = "RESERR",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(14, 1),
.end = AB5500_IRQ(14, 1),
},
{
.name = "FRAERR",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(14, 2),
.end = AB5500_IRQ(14, 2),
},
{
.name = "COMERR",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(14, 3),
.end = AB5500_IRQ(14, 3),
},
{
.name = "BSI_indicator",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(14, 4),
.end = AB5500_IRQ(14, 4),
},
{
.name = "SPDSET",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(14, 6),
.end = AB5500_IRQ(14, 6),
},
{
.name = "DSENT",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(14, 7),
.end = AB5500_IRQ(14, 7),
},
{
.name = "DREC",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(15, 0),
.end = AB5500_IRQ(15, 0),
},
{
.name = "ACCINT",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(15, 1),
.end = AB5500_IRQ(15, 1),
},
{
.name = "NOPINT",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(15, 2),
.end = AB5500_IRQ(15, 2),
},
},
},
[AB5500_DEVID_ONSWA] = {
.name = "ab5500-onswa",
.id = AB5500_DEVID_ONSWA,
.num_resources = 2,
.resources = (struct resource[]) {
{
.name = "ONSWAn_rising",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(1, 3),
.end = AB5500_IRQ(1, 3),
},
{
.name = "ONSWAn_falling",
.flags = IORESOURCE_IRQ,
.start = AB5500_IRQ(1, 4),
.end = AB5500_IRQ(1, 4),
},
},
},
};
/*
* Functionality for getting/setting register values.
*/
int ab5500_get_register_interruptible_raw(struct ab5500 *ab,
u8 bank, u8 reg,
u8 *value)
{
int err;
if (bank >= AB5500_NUM_BANKS)
return -EINVAL;
err = mutex_lock_interruptible(&ab->access_mutex);
if (err)
return err;
err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, reg, value, 1);
mutex_unlock(&ab->access_mutex);
return err;
}
static int get_register_page_interruptible(struct ab5500 *ab, u8 bank,
u8 first_reg, u8 *regvals, u8 numregs)
{
int err;
if (bank >= AB5500_NUM_BANKS)
return -EINVAL;
err = mutex_lock_interruptible(&ab->access_mutex);
if (err)
return err;
while (numregs) {
/* The hardware limit for get page is 4 */
u8 curnum = min_t(u8, numregs, 4u);
err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr,
first_reg, regvals, curnum);
if (err)
goto out;
numregs -= curnum;
first_reg += curnum;
regvals += curnum;
}
out:
mutex_unlock(&ab->access_mutex);
return err;
}
int ab5500_mask_and_set_register_interruptible_raw(struct ab5500 *ab, u8 bank,
u8 reg, u8 bitmask, u8 bitvalues)
{
int err = 0;
if (bank >= AB5500_NUM_BANKS)
return -EINVAL;
if (bitmask) {
u8 buf;
err = mutex_lock_interruptible(&ab->access_mutex);
if (err)
return err;
if (bitmask == 0xFF) /* No need to read in this case. */
buf = bitvalues;
else { /* Read and modify the register value. */
err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr,
reg, &buf, 1);
if (err)
return err;
buf = ((~bitmask & buf) | (bitmask & bitvalues));
}
/* Write the new value. */
err = db5500_prcmu_abb_write(bankinfo[bank].slave_addr, reg,
&buf, 1);
mutex_unlock(&ab->access_mutex);
}
return err;
}
static int
set_register_interruptible(struct ab5500 *ab, u8 bank, u8 reg, u8 value)
{
return ab5500_mask_and_set_register_interruptible_raw(ab, bank, reg,
0xff, value);
}
/*
* Read/write permission checking functions.
*/
static const struct ab5500_i2c_ranges *get_bankref(u8 devid, u8 bank)
{
u8 i;
if (devid < AB5500_NUM_DEVICES) {
for (i = 0; i < ab5500_bank_ranges[devid].nbanks; i++) {
if (ab5500_bank_ranges[devid].bank[i].bankid == bank)
return &ab5500_bank_ranges[devid].bank[i];
}
}
return NULL;
}
static bool page_write_allowed(u8 devid, u8 bank, u8 first_reg, u8 last_reg)
{
u8 i; /* range loop index */
const struct ab5500_i2c_ranges *bankref;
bankref = get_bankref(devid, bank);
if (bankref == NULL || last_reg < first_reg)
return false;
for (i = 0; i < bankref->nranges; i++) {
if (first_reg < bankref->range[i].first)
break;
if ((last_reg <= bankref->range[i].last) &&
(bankref->range[i].perm & AB5500_PERM_WR))
return true;
}
return false;
}
static bool reg_write_allowed(u8 devid, u8 bank, u8 reg)
{
return page_write_allowed(devid, bank, reg, reg);
}
static bool page_read_allowed(u8 devid, u8 bank, u8 first_reg, u8 last_reg)
{
u8 i;
const struct ab5500_i2c_ranges *bankref;
bankref = get_bankref(devid, bank);
if (bankref == NULL || last_reg < first_reg)
return false;
/* Find the range (if it exists in the list) that includes first_reg. */
for (i = 0; i < bankref->nranges; i++) {
if (first_reg < bankref->range[i].first)
return false;
if (first_reg <= bankref->range[i].last)
break;
}
/* Make sure that the entire range up to and including last_reg is
* readable. This may span several of the ranges in the list.
*/
while ((i < bankref->nranges) &&
(bankref->range[i].perm & AB5500_PERM_RD)) {
if (last_reg <= bankref->range[i].last)
return true;
if ((++i >= bankref->nranges) ||
(bankref->range[i].first !=
(bankref->range[i - 1].last + 1))) {
break;
}
}
return false;
}
static bool reg_read_allowed(u8 devid, u8 bank, u8 reg)
{
return page_read_allowed(devid, bank, reg, reg);
}
/*
* The exported register access functionality.
*/
static int ab5500_get_chip_id(struct device *dev)
{
struct ab5500 *ab = dev_get_drvdata(dev->parent);
return (int)ab->chip_id;
}
static int ab5500_mask_and_set_register_interruptible(struct device *dev,
u8 bank, u8 reg, u8 bitmask, u8 bitvalues)
{
struct ab5500 *ab;
struct platform_device *pdev = to_platform_device(dev);
if ((AB5500_NUM_BANKS <= bank) ||
!reg_write_allowed(pdev->id, bank, reg))
return -EINVAL;
ab = dev_get_drvdata(dev->parent);
return ab5500_mask_and_set_register_interruptible_raw(ab, bank, reg,
bitmask, bitvalues);
}
static int ab5500_set_register_interruptible(struct device *dev, u8 bank,
u8 reg, u8 value)
{
return ab5500_mask_and_set_register_interruptible(dev, bank, reg, 0xFF,
value);
}
static int ab5500_get_register_interruptible(struct device *dev, u8 bank,
u8 reg, u8 *value)
{
struct ab5500 *ab;
struct platform_device *pdev = to_platform_device(dev);
if ((AB5500_NUM_BANKS <= bank) ||
!reg_read_allowed(pdev->id, bank, reg))
return -EINVAL;
ab = dev_get_drvdata(dev->parent);
return ab5500_get_register_interruptible_raw(ab, bank, reg, value);
}
static int ab5500_get_register_page_interruptible(struct device *dev, u8 bank,
u8 first_reg, u8 *regvals, u8 numregs)
{
struct ab5500 *ab;
struct platform_device *pdev = to_platform_device(dev);
if ((AB5500_NUM_BANKS <= bank) ||
!page_read_allowed(pdev->id, bank,
first_reg, (first_reg + numregs - 1)))
return -EINVAL;
ab = dev_get_drvdata(dev->parent);
return get_register_page_interruptible(ab, bank, first_reg, regvals,
numregs);
}
static int
ab5500_event_registers_startup_state_get(struct device *dev, u8 *event)
{
struct ab5500 *ab;
ab = dev_get_drvdata(dev->parent);
if (!ab->startup_events_read)
return -EAGAIN; /* Try again later */
memcpy(event, ab->startup_events, AB5500_NUM_EVENT_REG);
return 0;
}
static struct abx500_ops ab5500_ops = {
.get_chip_id = ab5500_get_chip_id,
.get_register = ab5500_get_register_interruptible,
.set_register = ab5500_set_register_interruptible,
.get_register_page = ab5500_get_register_page_interruptible,
.set_register_page = NULL,
.mask_and_set_register = ab5500_mask_and_set_register_interruptible,
.event_registers_startup_state_get =
ab5500_event_registers_startup_state_get,
.startup_irq_enabled = NULL,
};
/*
* ab5500_setup : Basic set-up, datastructure creation/destruction
* and I2C interface.This sets up a default config
* in the AB5500 chip so that it will work as expected.
* @ab : Pointer to ab5500 structure
* @settings : Pointer to struct abx500_init_settings
* @size : Size of init data
*/
static int __init ab5500_setup(struct ab5500 *ab,
struct abx500_init_settings *settings, unsigned int size)
{
int err = 0;
int i;
for (i = 0; i < size; i++) {
err = ab5500_mask_and_set_register_interruptible_raw(ab,
settings[i].bank,
settings[i].reg,
0xFF, settings[i].setting);
if (err)
goto exit_no_setup;
/* If event mask register update the event mask in ab5500 */
if ((settings[i].bank == AB5500_BANK_IT) &&
(AB5500_MASK_BASE <= settings[i].reg) &&
(settings[i].reg <= AB5500_MASK_END)) {
ab->mask[settings[i].reg - AB5500_MASK_BASE] =
settings[i].setting;
}
}
exit_no_setup:
return err;
}
struct ab_family_id {
u8 id;
char *name;
};
static const struct ab_family_id ids[] __initdata = {
/* AB5500 */
{
.id = AB5500_1_0,
.name = "1.0"
},
{
.id = AB5500_1_1,
.name = "1.1"
},
/* Terminator */
{
.id = 0x00,
}
};
static int __init ab5500_probe(struct platform_device *pdev)
{
struct ab5500 *ab;
struct ab5500_platform_data *ab5500_plf_data =
pdev->dev.platform_data;
int err;
int i;
ab = kzalloc(sizeof(struct ab5500), GFP_KERNEL);
if (!ab) {
dev_err(&pdev->dev,
"could not allocate ab5500 device\n");
return -ENOMEM;
}
/* Initialize data structure */
mutex_init(&ab->access_mutex);
mutex_init(&ab->irq_lock);
ab->dev = &pdev->dev;
platform_set_drvdata(pdev, ab);
/* Read chip ID register */
err = ab5500_get_register_interruptible_raw(ab,
AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP,
AB5500_CHIP_ID, &ab->chip_id);
if (err) {
dev_err(&pdev->dev, "could not communicate with the analog "
"baseband chip\n");
goto exit_no_detect;
}
for (i = 0; ids[i].id != 0x0; i++) {
if (ids[i].id == ab->chip_id) {
snprintf(&ab->chip_name[0], sizeof(ab->chip_name) - 1,
"AB5500 %s", ids[i].name);
break;
}
}
if (ids[i].id == 0x0) {
dev_err(&pdev->dev, "unknown analog baseband chip id: 0x%x\n",
ab->chip_id);
dev_err(&pdev->dev, "driver not started!\n");
goto exit_no_detect;
}
/* Clear and mask all interrupts */
for (i = 0; i < AB5500_NUM_IRQ_REGS; i++) {
u8 latchreg = AB5500_IT_LATCH0_REG + i;
u8 maskreg = AB5500_IT_MASK0_REG + i;
u8 val;
ab5500_get_register_interruptible_raw(ab, AB5500_BANK_IT,
latchreg, &val);
set_register_interruptible(ab, AB5500_BANK_IT, maskreg, 0xff);
ab->mask[i] = ab->oldmask[i] = 0xff;
}
err = abx500_register_ops(&pdev->dev, &ab5500_ops);
if (err) {
dev_err(&pdev->dev, "ab5500_register ops error\n");
goto exit_no_detect;
}
/* Set up and register the platform devices. */
for (i = 0; i < AB5500_NUM_DEVICES; i++) {
ab5500_devs[i].platform_data = ab5500_plf_data->dev_data[i];
ab5500_devs[i].pdata_size =
sizeof(ab5500_plf_data->dev_data[i]);
}
err = mfd_add_devices(&pdev->dev, 0, ab5500_devs,
ARRAY_SIZE(ab5500_devs), NULL,
ab5500_plf_data->irq.base);
if (err) {
dev_err(&pdev->dev, "ab5500_mfd_add_device error\n");
goto exit_no_detect;
}
err = ab5500_setup(ab, ab5500_plf_data->init_settings,
ab5500_plf_data->init_settings_sz);
if (err) {
dev_err(&pdev->dev, "ab5500_setup error\n");
goto exit_no_detect;
}
ab5500_setup_debugfs(ab);
dev_info(&pdev->dev, "detected AB chip: %s\n", &ab->chip_name[0]);
return 0;
exit_no_detect:
kfree(ab);
return err;
}
static int __exit ab5500_remove(struct platform_device *pdev)
{
struct ab5500 *ab = platform_get_drvdata(pdev);
ab5500_remove_debugfs();
mfd_remove_devices(&pdev->dev);
kfree(ab);
return 0;
}
static struct platform_driver ab5500_driver = {
.driver = {
.name = "ab5500-core",
.owner = THIS_MODULE,
},
.remove = __exit_p(ab5500_remove),
};
static int __init ab5500_core_init(void)
{
return platform_driver_probe(&ab5500_driver, ab5500_probe);
}
static void __exit ab5500_core_exit(void)
{
platform_driver_unregister(&ab5500_driver);
}
subsys_initcall(ab5500_core_init);
module_exit(ab5500_core_exit);
MODULE_AUTHOR("Mattias Wallin <mattias.wallin@stericsson.com>");
MODULE_DESCRIPTION("AB5500 core driver");
MODULE_LICENSE("GPL");