 ee66e653ca
			
		
	
	
	ee66e653ca
	
	
	
		
			
			This moves all the header files related to the abx500 family into a common include directory below mfd. From now on we place any subchip header in that directory. Headers previously in e.g. <linux/mfd/ab8500/gpio.h> get prefixed and are now e.g. <linux/mfd/abx500/ab8500-gpio.h>. The top-level abstract interface remains in <linux/mfd/abx500.h>. Signed-off-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
		
			
				
	
	
		
			1440 lines
		
	
	
	
		
			30 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1440 lines
		
	
	
	
		
			30 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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/version.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");
 |