2009-05-22 05:17:06 +08:00
|
|
|
/*
|
2010-01-19 18:41:52 +08:00
|
|
|
* Copyright (C) 2007-2010 ST-Ericsson
|
2009-05-22 05:17:06 +08:00
|
|
|
* License terms: GNU General Public License (GPL) version 2
|
|
|
|
* Low-level core for exclusive access to the AB3100 IC on the I2C bus
|
|
|
|
* and some basic chip-configuration.
|
|
|
|
* Author: Linus Walleij <linus.walleij@stericsson.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/notifier.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2009-05-22 05:17:06 +08:00
|
|
|
#include <linux/err.h>
|
2011-07-04 03:13:27 +08:00
|
|
|
#include <linux/module.h>
|
2009-05-22 05:17:06 +08:00
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/interrupt.h>
|
2010-01-19 18:41:52 +08:00
|
|
|
#include <linux/random.h>
|
2009-05-22 05:17:06 +08:00
|
|
|
#include <linux/debugfs.h>
|
|
|
|
#include <linux/seq_file.h>
|
|
|
|
#include <linux/uaccess.h>
|
2010-08-20 16:27:12 +08:00
|
|
|
#include <linux/mfd/core.h>
|
2012-08-10 16:32:35 +08:00
|
|
|
#include <linux/mfd/ab3100.h>
|
2010-05-02 00:26:07 +08:00
|
|
|
#include <linux/mfd/abx500.h>
|
2009-05-22 05:17:06 +08:00
|
|
|
|
|
|
|
/* These are the only registers inside AB3100 used in this main file */
|
|
|
|
|
|
|
|
/* Interrupt event registers */
|
|
|
|
#define AB3100_EVENTA1 0x21
|
|
|
|
#define AB3100_EVENTA2 0x22
|
|
|
|
#define AB3100_EVENTA3 0x23
|
|
|
|
|
|
|
|
/* AB3100 DAC converter registers */
|
|
|
|
#define AB3100_DIS 0x00
|
|
|
|
#define AB3100_D0C 0x01
|
|
|
|
#define AB3100_D1C 0x02
|
|
|
|
#define AB3100_D2C 0x03
|
|
|
|
#define AB3100_D3C 0x04
|
|
|
|
|
|
|
|
/* Chip ID register */
|
|
|
|
#define AB3100_CID 0x20
|
|
|
|
|
|
|
|
/* AB3100 interrupt registers */
|
|
|
|
#define AB3100_IMRA1 0x24
|
|
|
|
#define AB3100_IMRA2 0x25
|
|
|
|
#define AB3100_IMRA3 0x26
|
|
|
|
#define AB3100_IMRB1 0x2B
|
|
|
|
#define AB3100_IMRB2 0x2C
|
|
|
|
#define AB3100_IMRB3 0x2D
|
|
|
|
|
|
|
|
/* System Power Monitoring and control registers */
|
|
|
|
#define AB3100_MCA 0x2E
|
|
|
|
#define AB3100_MCB 0x2F
|
|
|
|
|
|
|
|
/* SIM power up */
|
|
|
|
#define AB3100_SUP 0x50
|
|
|
|
|
|
|
|
/*
|
|
|
|
* I2C communication
|
|
|
|
*
|
|
|
|
* The AB3100 is usually assigned address 0x48 (7-bit)
|
|
|
|
* The chip is defined in the platform i2c_board_data section.
|
|
|
|
*/
|
2010-05-02 00:26:20 +08:00
|
|
|
static int ab3100_get_chip_id(struct device *dev)
|
2009-05-22 05:17:06 +08:00
|
|
|
{
|
2010-05-02 00:26:20 +08:00
|
|
|
struct ab3100 *ab3100 = dev_get_drvdata(dev->parent);
|
|
|
|
|
|
|
|
return (int)ab3100->chip_id;
|
2009-05-22 05:17:06 +08:00
|
|
|
}
|
|
|
|
|
2010-05-02 00:26:20 +08:00
|
|
|
static int ab3100_set_register_interruptible(struct ab3100 *ab3100,
|
|
|
|
u8 reg, u8 regval)
|
2009-05-22 05:17:06 +08:00
|
|
|
{
|
|
|
|
u8 regandval[2] = {reg, regval};
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = mutex_lock_interruptible(&ab3100->access_mutex);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A two-byte write message with the first byte containing the register
|
|
|
|
* number and the second byte containing the value to be written
|
|
|
|
* effectively sets a register in the AB3100.
|
|
|
|
*/
|
|
|
|
err = i2c_master_send(ab3100->i2c_client, regandval, 2);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (write register): %d\n",
|
|
|
|
err);
|
|
|
|
} else if (err != 2) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (write register) "
|
|
|
|
"%d bytes transferred (expected 2)\n",
|
|
|
|
err);
|
|
|
|
err = -EIO;
|
|
|
|
} else {
|
|
|
|
/* All is well */
|
|
|
|
err = 0;
|
|
|
|
}
|
|
|
|
mutex_unlock(&ab3100->access_mutex);
|
2009-08-13 17:49:38 +08:00
|
|
|
return err;
|
2009-05-22 05:17:06 +08:00
|
|
|
}
|
2009-08-13 17:49:23 +08:00
|
|
|
|
2010-05-02 00:26:20 +08:00
|
|
|
static int set_register_interruptible(struct device *dev,
|
|
|
|
u8 bank, u8 reg, u8 value)
|
|
|
|
{
|
|
|
|
struct ab3100 *ab3100 = dev_get_drvdata(dev->parent);
|
|
|
|
|
|
|
|
return ab3100_set_register_interruptible(ab3100, reg, value);
|
|
|
|
}
|
2009-05-22 05:17:06 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The test registers exist at an I2C bus address up one
|
|
|
|
* from the ordinary base. They are not supposed to be used
|
|
|
|
* in production code, but sometimes you have to do that
|
|
|
|
* anyway. It's currently only used from this file so declare
|
|
|
|
* it static and do not export.
|
|
|
|
*/
|
2009-08-13 17:49:23 +08:00
|
|
|
static int ab3100_set_test_register_interruptible(struct ab3100 *ab3100,
|
2009-05-22 05:17:06 +08:00
|
|
|
u8 reg, u8 regval)
|
|
|
|
{
|
|
|
|
u8 regandval[2] = {reg, regval};
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = mutex_lock_interruptible(&ab3100->access_mutex);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = i2c_master_send(ab3100->testreg_client, regandval, 2);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (write test register): %d\n",
|
|
|
|
err);
|
|
|
|
} else if (err != 2) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (write test register) "
|
|
|
|
"%d bytes transferred (expected 2)\n",
|
|
|
|
err);
|
|
|
|
err = -EIO;
|
|
|
|
} else {
|
|
|
|
/* All is well */
|
|
|
|
err = 0;
|
|
|
|
}
|
|
|
|
mutex_unlock(&ab3100->access_mutex);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2010-05-02 00:26:20 +08:00
|
|
|
static int ab3100_get_register_interruptible(struct ab3100 *ab3100,
|
2010-08-20 16:27:12 +08:00
|
|
|
u8 reg, u8 *regval)
|
2009-05-22 05:17:06 +08:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = mutex_lock_interruptible(&ab3100->access_mutex);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* AB3100 require an I2C "stop" command between each message, else
|
|
|
|
* it will not work. The only way of achieveing this with the
|
|
|
|
* message transport layer is to send the read and write messages
|
|
|
|
* separately.
|
|
|
|
*/
|
|
|
|
err = i2c_master_send(ab3100->i2c_client, ®, 1);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (send register address): %d\n",
|
|
|
|
err);
|
|
|
|
goto get_reg_out_unlock;
|
|
|
|
} else if (err != 1) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (send register address) "
|
|
|
|
"%d bytes transferred (expected 1)\n",
|
|
|
|
err);
|
|
|
|
err = -EIO;
|
|
|
|
goto get_reg_out_unlock;
|
|
|
|
} else {
|
|
|
|
/* All is well */
|
|
|
|
err = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = i2c_master_recv(ab3100->i2c_client, regval, 1);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (read register): %d\n",
|
|
|
|
err);
|
|
|
|
goto get_reg_out_unlock;
|
|
|
|
} else if (err != 1) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (read register) "
|
|
|
|
"%d bytes transferred (expected 1)\n",
|
|
|
|
err);
|
|
|
|
err = -EIO;
|
|
|
|
goto get_reg_out_unlock;
|
|
|
|
} else {
|
|
|
|
/* All is well */
|
|
|
|
err = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
get_reg_out_unlock:
|
|
|
|
mutex_unlock(&ab3100->access_mutex);
|
|
|
|
return err;
|
|
|
|
}
|
2009-08-13 17:49:23 +08:00
|
|
|
|
2010-05-02 00:26:20 +08:00
|
|
|
static int get_register_interruptible(struct device *dev, u8 bank, u8 reg,
|
2010-08-20 16:27:12 +08:00
|
|
|
u8 *value)
|
2010-05-02 00:26:20 +08:00
|
|
|
{
|
|
|
|
struct ab3100 *ab3100 = dev_get_drvdata(dev->parent);
|
|
|
|
|
|
|
|
return ab3100_get_register_interruptible(ab3100, reg, value);
|
|
|
|
}
|
2009-05-22 05:17:06 +08:00
|
|
|
|
2010-05-02 00:26:20 +08:00
|
|
|
static int ab3100_get_register_page_interruptible(struct ab3100 *ab3100,
|
2009-05-22 05:17:06 +08:00
|
|
|
u8 first_reg, u8 *regvals, u8 numregs)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (ab3100->chip_id == 0xa0 ||
|
|
|
|
ab3100->chip_id == 0xa1)
|
|
|
|
/* These don't support paged reads */
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
err = mutex_lock_interruptible(&ab3100->access_mutex);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Paged read also require an I2C "stop" command.
|
|
|
|
*/
|
|
|
|
err = i2c_master_send(ab3100->i2c_client, &first_reg, 1);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (send first register address): %d\n",
|
|
|
|
err);
|
|
|
|
goto get_reg_page_out_unlock;
|
|
|
|
} else if (err != 1) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (send first register address) "
|
|
|
|
"%d bytes transferred (expected 1)\n",
|
|
|
|
err);
|
|
|
|
err = -EIO;
|
|
|
|
goto get_reg_page_out_unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = i2c_master_recv(ab3100->i2c_client, regvals, numregs);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (read register page): %d\n",
|
|
|
|
err);
|
|
|
|
goto get_reg_page_out_unlock;
|
|
|
|
} else if (err != numregs) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (read register page) "
|
|
|
|
"%d bytes transferred (expected %d)\n",
|
|
|
|
err, numregs);
|
|
|
|
err = -EIO;
|
|
|
|
goto get_reg_page_out_unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* All is well */
|
|
|
|
err = 0;
|
|
|
|
|
|
|
|
get_reg_page_out_unlock:
|
|
|
|
mutex_unlock(&ab3100->access_mutex);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2010-05-02 00:26:20 +08:00
|
|
|
static int get_register_page_interruptible(struct device *dev, u8 bank,
|
|
|
|
u8 first_reg, u8 *regvals, u8 numregs)
|
|
|
|
{
|
|
|
|
struct ab3100 *ab3100 = dev_get_drvdata(dev->parent);
|
|
|
|
|
|
|
|
return ab3100_get_register_page_interruptible(ab3100,
|
|
|
|
first_reg, regvals, numregs);
|
|
|
|
}
|
2009-08-13 17:49:23 +08:00
|
|
|
|
2010-05-02 00:26:20 +08:00
|
|
|
static int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100,
|
2009-05-22 05:17:06 +08:00
|
|
|
u8 reg, u8 andmask, u8 ormask)
|
|
|
|
{
|
|
|
|
u8 regandval[2] = {reg, 0};
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = mutex_lock_interruptible(&ab3100->access_mutex);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* First read out the target register */
|
|
|
|
err = i2c_master_send(ab3100->i2c_client, ®, 1);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (maskset send address): %d\n",
|
|
|
|
err);
|
|
|
|
goto get_maskset_unlock;
|
|
|
|
} else if (err != 1) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (maskset send address) "
|
|
|
|
"%d bytes transferred (expected 1)\n",
|
|
|
|
err);
|
|
|
|
err = -EIO;
|
|
|
|
goto get_maskset_unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = i2c_master_recv(ab3100->i2c_client, ®andval[1], 1);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (maskset read register): %d\n",
|
|
|
|
err);
|
|
|
|
goto get_maskset_unlock;
|
|
|
|
} else if (err != 1) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (maskset read register) "
|
|
|
|
"%d bytes transferred (expected 1)\n",
|
|
|
|
err);
|
|
|
|
err = -EIO;
|
|
|
|
goto get_maskset_unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Modify the register */
|
|
|
|
regandval[1] &= andmask;
|
|
|
|
regandval[1] |= ormask;
|
|
|
|
|
|
|
|
/* Write the register */
|
|
|
|
err = i2c_master_send(ab3100->i2c_client, regandval, 2);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (write register): %d\n",
|
|
|
|
err);
|
|
|
|
goto get_maskset_unlock;
|
|
|
|
} else if (err != 2) {
|
|
|
|
dev_err(ab3100->dev,
|
|
|
|
"write error (write register) "
|
|
|
|
"%d bytes transferred (expected 2)\n",
|
|
|
|
err);
|
|
|
|
err = -EIO;
|
|
|
|
goto get_maskset_unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* All is well */
|
|
|
|
err = 0;
|
|
|
|
|
|
|
|
get_maskset_unlock:
|
|
|
|
mutex_unlock(&ab3100->access_mutex);
|
|
|
|
return err;
|
|
|
|
}
|
2009-08-13 17:49:23 +08:00
|
|
|
|
2010-05-02 00:26:20 +08:00
|
|
|
static int mask_and_set_register_interruptible(struct device *dev, u8 bank,
|
|
|
|
u8 reg, u8 bitmask, u8 bitvalues)
|
|
|
|
{
|
|
|
|
struct ab3100 *ab3100 = dev_get_drvdata(dev->parent);
|
|
|
|
|
|
|
|
return ab3100_mask_and_set_register_interruptible(ab3100,
|
|
|
|
reg, bitmask, (bitmask & bitvalues));
|
|
|
|
}
|
2009-05-22 05:17:06 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Register a simple callback for handling any AB3100 events.
|
|
|
|
*/
|
|
|
|
int ab3100_event_register(struct ab3100 *ab3100,
|
|
|
|
struct notifier_block *nb)
|
|
|
|
{
|
|
|
|
return blocking_notifier_chain_register(&ab3100->event_subscribers,
|
|
|
|
nb);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ab3100_event_register);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Remove a previously registered callback.
|
|
|
|
*/
|
|
|
|
int ab3100_event_unregister(struct ab3100 *ab3100,
|
|
|
|
struct notifier_block *nb)
|
|
|
|
{
|
|
|
|
return blocking_notifier_chain_unregister(&ab3100->event_subscribers,
|
|
|
|
nb);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ab3100_event_unregister);
|
|
|
|
|
|
|
|
|
2010-05-02 00:26:20 +08:00
|
|
|
static int ab3100_event_registers_startup_state_get(struct device *dev,
|
|
|
|
u8 *event)
|
2009-05-22 05:17:06 +08:00
|
|
|
{
|
2010-05-02 00:26:20 +08:00
|
|
|
struct ab3100 *ab3100 = dev_get_drvdata(dev->parent);
|
2009-05-22 05:17:06 +08:00
|
|
|
if (!ab3100->startup_events_read)
|
|
|
|
return -EAGAIN; /* Try again later */
|
2010-05-02 00:26:20 +08:00
|
|
|
memcpy(event, ab3100->startup_events, 3);
|
2009-05-22 05:17:06 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2010-05-02 00:26:20 +08:00
|
|
|
|
|
|
|
static struct abx500_ops ab3100_ops = {
|
|
|
|
.get_chip_id = ab3100_get_chip_id,
|
|
|
|
.set_register = set_register_interruptible,
|
|
|
|
.get_register = get_register_interruptible,
|
|
|
|
.get_register_page = get_register_page_interruptible,
|
|
|
|
.set_register_page = NULL,
|
|
|
|
.mask_and_set_register = mask_and_set_register_interruptible,
|
|
|
|
.event_registers_startup_state_get =
|
|
|
|
ab3100_event_registers_startup_state_get,
|
|
|
|
.startup_irq_enabled = NULL,
|
|
|
|
};
|
2009-05-22 05:17:06 +08:00
|
|
|
|
2010-01-08 17:44:16 +08:00
|
|
|
/*
|
|
|
|
* This is a threaded interrupt handler so we can make some
|
|
|
|
* I2C calls etc.
|
|
|
|
*/
|
|
|
|
static irqreturn_t ab3100_irq_handler(int irq, void *data)
|
2009-05-22 05:17:06 +08:00
|
|
|
{
|
2010-01-08 17:44:16 +08:00
|
|
|
struct ab3100 *ab3100 = data;
|
2009-05-22 05:17:06 +08:00
|
|
|
u8 event_regs[3];
|
|
|
|
u32 fatevent;
|
|
|
|
int err;
|
|
|
|
|
2009-08-13 17:49:23 +08:00
|
|
|
err = ab3100_get_register_page_interruptible(ab3100, AB3100_EVENTA1,
|
2009-05-22 05:17:06 +08:00
|
|
|
event_regs, 3);
|
|
|
|
if (err)
|
2010-01-08 17:44:16 +08:00
|
|
|
goto err_event;
|
2009-05-22 05:17:06 +08:00
|
|
|
|
|
|
|
fatevent = (event_regs[0] << 16) |
|
|
|
|
(event_regs[1] << 8) |
|
|
|
|
event_regs[2];
|
|
|
|
|
|
|
|
if (!ab3100->startup_events_read) {
|
2010-05-02 00:26:20 +08:00
|
|
|
ab3100->startup_events[0] = event_regs[0];
|
|
|
|
ab3100->startup_events[1] = event_regs[1];
|
|
|
|
ab3100->startup_events[2] = event_regs[2];
|
2009-05-22 05:17:06 +08:00
|
|
|
ab3100->startup_events_read = true;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* The notified parties will have to mask out the events
|
|
|
|
* they're interested in and react to them. They will be
|
|
|
|
* notified on all events, then they use the fatevent value
|
|
|
|
* to determine if they're interested.
|
|
|
|
*/
|
|
|
|
blocking_notifier_call_chain(&ab3100->event_subscribers,
|
|
|
|
fatevent, NULL);
|
|
|
|
|
|
|
|
dev_dbg(ab3100->dev,
|
|
|
|
"IRQ Event: 0x%08x\n", fatevent);
|
|
|
|
|
2010-01-08 17:44:16 +08:00
|
|
|
return IRQ_HANDLED;
|
2009-05-22 05:17:06 +08:00
|
|
|
|
2010-01-08 17:44:16 +08:00
|
|
|
err_event:
|
2009-05-22 05:17:06 +08:00
|
|
|
dev_dbg(ab3100->dev,
|
2010-01-08 17:44:16 +08:00
|
|
|
"error reading event status\n");
|
2009-05-22 05:17:06 +08:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
|
|
/*
|
|
|
|
* Some debugfs entries only exposed if we're using debug
|
|
|
|
*/
|
|
|
|
static int ab3100_registers_print(struct seq_file *s, void *p)
|
|
|
|
{
|
|
|
|
struct ab3100 *ab3100 = s->private;
|
|
|
|
u8 value;
|
|
|
|
u8 reg;
|
|
|
|
|
|
|
|
seq_printf(s, "AB3100 registers:\n");
|
|
|
|
|
|
|
|
for (reg = 0; reg < 0xff; reg++) {
|
2009-08-13 17:49:23 +08:00
|
|
|
ab3100_get_register_interruptible(ab3100, reg, &value);
|
2009-05-22 05:17:06 +08:00
|
|
|
seq_printf(s, "[0x%x]: 0x%x\n", reg, value);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ab3100_registers_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
return single_open(file, ab3100_registers_print, inode->i_private);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct file_operations ab3100_registers_fops = {
|
|
|
|
.open = ab3100_registers_open,
|
|
|
|
.read = seq_read,
|
|
|
|
.llseek = seq_lseek,
|
|
|
|
.release = single_release,
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ab3100_get_set_reg_priv {
|
|
|
|
struct ab3100 *ab3100;
|
|
|
|
bool mode;
|
|
|
|
};
|
|
|
|
|
2009-06-23 16:48:36 +08:00
|
|
|
static ssize_t ab3100_get_set_reg(struct file *file,
|
|
|
|
const char __user *user_buf,
|
|
|
|
size_t count, loff_t *ppos)
|
2009-05-22 05:17:06 +08:00
|
|
|
{
|
|
|
|
struct ab3100_get_set_reg_priv *priv = file->private_data;
|
|
|
|
struct ab3100 *ab3100 = priv->ab3100;
|
|
|
|
char buf[32];
|
2009-06-23 16:48:36 +08:00
|
|
|
ssize_t buf_size;
|
2009-05-22 05:17:06 +08:00
|
|
|
int regp;
|
2013-06-04 12:11:50 +08:00
|
|
|
u8 user_reg;
|
2009-05-22 05:17:06 +08:00
|
|
|
int err;
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
/* Get userspace string and assure termination */
|
|
|
|
buf_size = min(count, (sizeof(buf)-1));
|
|
|
|
if (copy_from_user(buf, user_buf, buf_size))
|
|
|
|
return -EFAULT;
|
|
|
|
buf[buf_size] = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The idea is here to parse a string which is either
|
|
|
|
* "0xnn" for reading a register, or "0xaa 0xbb" for
|
|
|
|
* writing 0xbb to the register 0xaa. First move past
|
|
|
|
* whitespace and then begin to parse the register.
|
|
|
|
*/
|
|
|
|
while ((i < buf_size) && (buf[i] == ' '))
|
|
|
|
i++;
|
|
|
|
regp = i;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Advance pointer to end of string then terminate
|
|
|
|
* the register string. This is needed to satisfy
|
2013-06-04 12:11:50 +08:00
|
|
|
* the kstrtou8() function.
|
2009-05-22 05:17:06 +08:00
|
|
|
*/
|
|
|
|
while ((i < buf_size) && (buf[i] != ' '))
|
|
|
|
i++;
|
|
|
|
buf[i] = '\0';
|
|
|
|
|
2013-06-04 12:11:50 +08:00
|
|
|
err = kstrtou8(&buf[regp], 16, &user_reg);
|
2009-05-22 05:17:06 +08:00
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* Either we read or we write a register here */
|
|
|
|
if (!priv->mode) {
|
|
|
|
/* Reading */
|
|
|
|
u8 regvalue;
|
|
|
|
|
2013-06-04 12:11:50 +08:00
|
|
|
ab3100_get_register_interruptible(ab3100, user_reg, ®value);
|
2009-05-22 05:17:06 +08:00
|
|
|
|
|
|
|
dev_info(ab3100->dev,
|
|
|
|
"debug read AB3100 reg[0x%02x]: 0x%02x\n",
|
2013-06-04 12:11:50 +08:00
|
|
|
user_reg, regvalue);
|
2009-05-22 05:17:06 +08:00
|
|
|
} else {
|
|
|
|
int valp;
|
2013-06-04 12:11:50 +08:00
|
|
|
u8 user_value;
|
2009-05-22 05:17:06 +08:00
|
|
|
u8 regvalue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Writing, we need some value to write to
|
|
|
|
* the register so keep parsing the string
|
|
|
|
* from userspace.
|
|
|
|
*/
|
|
|
|
i++;
|
|
|
|
while ((i < buf_size) && (buf[i] == ' '))
|
|
|
|
i++;
|
|
|
|
valp = i;
|
|
|
|
while ((i < buf_size) && (buf[i] != ' '))
|
|
|
|
i++;
|
|
|
|
buf[i] = '\0';
|
|
|
|
|
2013-06-04 12:11:50 +08:00
|
|
|
err = kstrtou8(&buf[valp], 16, &user_value);
|
2009-05-22 05:17:06 +08:00
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2013-06-04 12:11:50 +08:00
|
|
|
ab3100_set_register_interruptible(ab3100, user_reg, user_value);
|
|
|
|
ab3100_get_register_interruptible(ab3100, user_reg, ®value);
|
2009-05-22 05:17:06 +08:00
|
|
|
|
|
|
|
dev_info(ab3100->dev,
|
|
|
|
"debug write reg[0x%02x] with 0x%02x, "
|
|
|
|
"after readback: 0x%02x\n",
|
2013-06-04 12:11:50 +08:00
|
|
|
user_reg, user_value, regvalue);
|
2009-05-22 05:17:06 +08:00
|
|
|
}
|
|
|
|
return buf_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct file_operations ab3100_get_set_reg_fops = {
|
2012-04-06 05:25:11 +08:00
|
|
|
.open = simple_open,
|
2009-05-22 05:17:06 +08:00
|
|
|
.write = ab3100_get_set_reg,
|
llseek: automatically add .llseek fop
All file_operations should get a .llseek operation so we can make
nonseekable_open the default for future file operations without a
.llseek pointer.
The three cases that we can automatically detect are no_llseek, seq_lseek
and default_llseek. For cases where we can we can automatically prove that
the file offset is always ignored, we use noop_llseek, which maintains
the current behavior of not returning an error from a seek.
New drivers should normally not use noop_llseek but instead use no_llseek
and call nonseekable_open at open time. Existing drivers can be converted
to do the same when the maintainer knows for certain that no user code
relies on calling seek on the device file.
The generated code is often incorrectly indented and right now contains
comments that clarify for each added line why a specific variant was
chosen. In the version that gets submitted upstream, the comments will
be gone and I will manually fix the indentation, because there does not
seem to be a way to do that using coccinelle.
Some amount of new code is currently sitting in linux-next that should get
the same modifications, which I will do at the end of the merge window.
Many thanks to Julia Lawall for helping me learn to write a semantic
patch that does all this.
===== begin semantic patch =====
// This adds an llseek= method to all file operations,
// as a preparation for making no_llseek the default.
//
// The rules are
// - use no_llseek explicitly if we do nonseekable_open
// - use seq_lseek for sequential files
// - use default_llseek if we know we access f_pos
// - use noop_llseek if we know we don't access f_pos,
// but we still want to allow users to call lseek
//
@ open1 exists @
identifier nested_open;
@@
nested_open(...)
{
<+...
nonseekable_open(...)
...+>
}
@ open exists@
identifier open_f;
identifier i, f;
identifier open1.nested_open;
@@
int open_f(struct inode *i, struct file *f)
{
<+...
(
nonseekable_open(...)
|
nested_open(...)
)
...+>
}
@ read disable optional_qualifier exists @
identifier read_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
expression E;
identifier func;
@@
ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off)
{
<+...
(
*off = E
|
*off += E
|
func(..., off, ...)
|
E = *off
)
...+>
}
@ read_no_fpos disable optional_qualifier exists @
identifier read_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
@@
ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off)
{
... when != off
}
@ write @
identifier write_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
expression E;
identifier func;
@@
ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off)
{
<+...
(
*off = E
|
*off += E
|
func(..., off, ...)
|
E = *off
)
...+>
}
@ write_no_fpos @
identifier write_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
@@
ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off)
{
... when != off
}
@ fops0 @
identifier fops;
@@
struct file_operations fops = {
...
};
@ has_llseek depends on fops0 @
identifier fops0.fops;
identifier llseek_f;
@@
struct file_operations fops = {
...
.llseek = llseek_f,
...
};
@ has_read depends on fops0 @
identifier fops0.fops;
identifier read_f;
@@
struct file_operations fops = {
...
.read = read_f,
...
};
@ has_write depends on fops0 @
identifier fops0.fops;
identifier write_f;
@@
struct file_operations fops = {
...
.write = write_f,
...
};
@ has_open depends on fops0 @
identifier fops0.fops;
identifier open_f;
@@
struct file_operations fops = {
...
.open = open_f,
...
};
// use no_llseek if we call nonseekable_open
////////////////////////////////////////////
@ nonseekable1 depends on !has_llseek && has_open @
identifier fops0.fops;
identifier nso ~= "nonseekable_open";
@@
struct file_operations fops = {
... .open = nso, ...
+.llseek = no_llseek, /* nonseekable */
};
@ nonseekable2 depends on !has_llseek @
identifier fops0.fops;
identifier open.open_f;
@@
struct file_operations fops = {
... .open = open_f, ...
+.llseek = no_llseek, /* open uses nonseekable */
};
// use seq_lseek for sequential files
/////////////////////////////////////
@ seq depends on !has_llseek @
identifier fops0.fops;
identifier sr ~= "seq_read";
@@
struct file_operations fops = {
... .read = sr, ...
+.llseek = seq_lseek, /* we have seq_read */
};
// use default_llseek if there is a readdir
///////////////////////////////////////////
@ fops1 depends on !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier readdir_e;
@@
// any other fop is used that changes pos
struct file_operations fops = {
... .readdir = readdir_e, ...
+.llseek = default_llseek, /* readdir is present */
};
// use default_llseek if at least one of read/write touches f_pos
/////////////////////////////////////////////////////////////////
@ fops2 depends on !fops1 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read.read_f;
@@
// read fops use offset
struct file_operations fops = {
... .read = read_f, ...
+.llseek = default_llseek, /* read accesses f_pos */
};
@ fops3 depends on !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier write.write_f;
@@
// write fops use offset
struct file_operations fops = {
... .write = write_f, ...
+ .llseek = default_llseek, /* write accesses f_pos */
};
// Use noop_llseek if neither read nor write accesses f_pos
///////////////////////////////////////////////////////////
@ fops4 depends on !fops1 && !fops2 && !fops3 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read_no_fpos.read_f;
identifier write_no_fpos.write_f;
@@
// write fops use offset
struct file_operations fops = {
...
.write = write_f,
.read = read_f,
...
+.llseek = noop_llseek, /* read and write both use no f_pos */
};
@ depends on has_write && !has_read && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier write_no_fpos.write_f;
@@
struct file_operations fops = {
... .write = write_f, ...
+.llseek = noop_llseek, /* write uses no f_pos */
};
@ depends on has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read_no_fpos.read_f;
@@
struct file_operations fops = {
... .read = read_f, ...
+.llseek = noop_llseek, /* read uses no f_pos */
};
@ depends on !has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
@@
struct file_operations fops = {
...
+.llseek = noop_llseek, /* no read or write fn */
};
===== End semantic patch =====
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Julia Lawall <julia@diku.dk>
Cc: Christoph Hellwig <hch@infradead.org>
2010-08-16 00:52:59 +08:00
|
|
|
.llseek = noop_llseek,
|
2009-05-22 05:17:06 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct dentry *ab3100_dir;
|
|
|
|
static struct dentry *ab3100_reg_file;
|
|
|
|
static struct ab3100_get_set_reg_priv ab3100_get_priv;
|
|
|
|
static struct dentry *ab3100_get_reg_file;
|
|
|
|
static struct ab3100_get_set_reg_priv ab3100_set_priv;
|
|
|
|
static struct dentry *ab3100_set_reg_file;
|
|
|
|
|
|
|
|
static void ab3100_setup_debugfs(struct ab3100 *ab3100)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
ab3100_dir = debugfs_create_dir("ab3100", NULL);
|
|
|
|
if (!ab3100_dir)
|
|
|
|
goto exit_no_debugfs;
|
|
|
|
|
|
|
|
ab3100_reg_file = debugfs_create_file("registers",
|
|
|
|
S_IRUGO, ab3100_dir, ab3100,
|
|
|
|
&ab3100_registers_fops);
|
|
|
|
if (!ab3100_reg_file) {
|
|
|
|
err = -ENOMEM;
|
|
|
|
goto exit_destroy_dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
ab3100_get_priv.ab3100 = ab3100;
|
|
|
|
ab3100_get_priv.mode = false;
|
|
|
|
ab3100_get_reg_file = debugfs_create_file("get_reg",
|
2011-02-04 20:23:36 +08:00
|
|
|
S_IWUSR, ab3100_dir, &ab3100_get_priv,
|
2009-05-22 05:17:06 +08:00
|
|
|
&ab3100_get_set_reg_fops);
|
|
|
|
if (!ab3100_get_reg_file) {
|
|
|
|
err = -ENOMEM;
|
|
|
|
goto exit_destroy_reg;
|
|
|
|
}
|
|
|
|
|
|
|
|
ab3100_set_priv.ab3100 = ab3100;
|
|
|
|
ab3100_set_priv.mode = true;
|
|
|
|
ab3100_set_reg_file = debugfs_create_file("set_reg",
|
2011-02-04 20:23:36 +08:00
|
|
|
S_IWUSR, ab3100_dir, &ab3100_set_priv,
|
2009-05-22 05:17:06 +08:00
|
|
|
&ab3100_get_set_reg_fops);
|
|
|
|
if (!ab3100_set_reg_file) {
|
|
|
|
err = -ENOMEM;
|
|
|
|
goto exit_destroy_get_reg;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
exit_destroy_get_reg:
|
|
|
|
debugfs_remove(ab3100_get_reg_file);
|
|
|
|
exit_destroy_reg:
|
|
|
|
debugfs_remove(ab3100_reg_file);
|
|
|
|
exit_destroy_dir:
|
|
|
|
debugfs_remove(ab3100_dir);
|
|
|
|
exit_no_debugfs:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
static inline void ab3100_remove_debugfs(void)
|
|
|
|
{
|
|
|
|
debugfs_remove(ab3100_set_reg_file);
|
|
|
|
debugfs_remove(ab3100_get_reg_file);
|
|
|
|
debugfs_remove(ab3100_reg_file);
|
|
|
|
debugfs_remove(ab3100_dir);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void ab3100_setup_debugfs(struct ab3100 *ab3100)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
static inline void ab3100_remove_debugfs(void)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Basic set-up, datastructure creation/destruction and I2C interface.
|
|
|
|
* This sets up a default config in the AB3100 chip so that it
|
|
|
|
* will work as expected.
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct ab3100_init_setting {
|
|
|
|
u8 abreg;
|
|
|
|
u8 setting;
|
|
|
|
};
|
|
|
|
|
2012-11-20 02:25:09 +08:00
|
|
|
static const struct ab3100_init_setting ab3100_init_settings[] = {
|
2009-05-22 05:17:06 +08:00
|
|
|
{
|
|
|
|
.abreg = AB3100_MCA,
|
|
|
|
.setting = 0x01
|
|
|
|
}, {
|
|
|
|
.abreg = AB3100_MCB,
|
|
|
|
.setting = 0x30
|
|
|
|
}, {
|
|
|
|
.abreg = AB3100_IMRA1,
|
|
|
|
.setting = 0x00
|
|
|
|
}, {
|
|
|
|
.abreg = AB3100_IMRA2,
|
|
|
|
.setting = 0xFF
|
|
|
|
}, {
|
|
|
|
.abreg = AB3100_IMRA3,
|
|
|
|
.setting = 0x01
|
|
|
|
}, {
|
|
|
|
.abreg = AB3100_IMRB1,
|
2009-08-13 17:49:49 +08:00
|
|
|
.setting = 0xBF
|
2009-05-22 05:17:06 +08:00
|
|
|
}, {
|
|
|
|
.abreg = AB3100_IMRB2,
|
|
|
|
.setting = 0xFF
|
|
|
|
}, {
|
|
|
|
.abreg = AB3100_IMRB3,
|
|
|
|
.setting = 0xFF
|
|
|
|
}, {
|
|
|
|
.abreg = AB3100_SUP,
|
|
|
|
.setting = 0x00
|
|
|
|
}, {
|
|
|
|
.abreg = AB3100_DIS,
|
|
|
|
.setting = 0xF0
|
|
|
|
}, {
|
|
|
|
.abreg = AB3100_D0C,
|
|
|
|
.setting = 0x00
|
|
|
|
}, {
|
|
|
|
.abreg = AB3100_D1C,
|
|
|
|
.setting = 0x00
|
|
|
|
}, {
|
|
|
|
.abreg = AB3100_D2C,
|
|
|
|
.setting = 0x00
|
|
|
|
}, {
|
|
|
|
.abreg = AB3100_D3C,
|
|
|
|
.setting = 0x00
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2012-11-20 02:23:04 +08:00
|
|
|
static int ab3100_setup(struct ab3100 *ab3100)
|
2009-05-22 05:17:06 +08:00
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ab3100_init_settings); i++) {
|
2009-08-13 17:49:23 +08:00
|
|
|
err = ab3100_set_register_interruptible(ab3100,
|
2009-05-22 05:17:06 +08:00
|
|
|
ab3100_init_settings[i].abreg,
|
|
|
|
ab3100_init_settings[i].setting);
|
|
|
|
if (err)
|
|
|
|
goto exit_no_setup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Special trick to make the AB3100 use the 32kHz clock (RTC)
|
2009-08-13 17:49:23 +08:00
|
|
|
* bit 3 in test register 0x02 is a special, undocumented test
|
2009-05-22 05:17:06 +08:00
|
|
|
* register bit that only exist in AB3100 P1E
|
|
|
|
*/
|
|
|
|
if (ab3100->chip_id == 0xc4) {
|
|
|
|
dev_warn(ab3100->dev,
|
|
|
|
"AB3100 P1E variant detected, "
|
|
|
|
"forcing chip to 32KHz\n");
|
2010-05-02 00:26:20 +08:00
|
|
|
err = ab3100_set_test_register_interruptible(ab3100,
|
|
|
|
0x02, 0x08);
|
2009-05-22 05:17:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
exit_no_setup:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2010-08-20 16:27:12 +08:00
|
|
|
/* The subdevices of the AB3100 */
|
|
|
|
static struct mfd_cell ab3100_devs[] = {
|
|
|
|
{
|
|
|
|
.name = "ab3100-dac",
|
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-leds",
|
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-power",
|
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-regulators",
|
2013-04-22 17:57:25 +08:00
|
|
|
.of_compatible = "stericsson,ab3100-regulators",
|
2010-08-20 16:27:12 +08:00
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-sim",
|
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-uart",
|
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-rtc",
|
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-charger",
|
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-boost",
|
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-adc",
|
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-fuelgauge",
|
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-vibrator",
|
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-otp",
|
|
|
|
.id = -1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "ab3100-codec",
|
|
|
|
.id = -1,
|
|
|
|
},
|
2009-05-22 05:17:06 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
struct ab_family_id {
|
|
|
|
u8 id;
|
|
|
|
char *name;
|
|
|
|
};
|
|
|
|
|
2012-11-20 02:25:09 +08:00
|
|
|
static const struct ab_family_id ids[] = {
|
2009-05-22 05:17:06 +08:00
|
|
|
/* AB3100 */
|
|
|
|
{
|
|
|
|
.id = 0xc0,
|
|
|
|
.name = "P1A"
|
|
|
|
}, {
|
|
|
|
.id = 0xc1,
|
|
|
|
.name = "P1B"
|
|
|
|
}, {
|
|
|
|
.id = 0xc2,
|
|
|
|
.name = "P1C"
|
|
|
|
}, {
|
|
|
|
.id = 0xc3,
|
|
|
|
.name = "P1D"
|
|
|
|
}, {
|
|
|
|
.id = 0xc4,
|
|
|
|
.name = "P1E"
|
|
|
|
}, {
|
|
|
|
.id = 0xc5,
|
|
|
|
.name = "P1F/R1A"
|
|
|
|
}, {
|
|
|
|
.id = 0xc6,
|
|
|
|
.name = "P1G/R1A"
|
|
|
|
}, {
|
|
|
|
.id = 0xc7,
|
|
|
|
.name = "P2A/R2A"
|
|
|
|
}, {
|
|
|
|
.id = 0xc8,
|
|
|
|
.name = "P2B/R2B"
|
|
|
|
},
|
|
|
|
/* AB3000 variants, not supported */
|
|
|
|
{
|
|
|
|
.id = 0xa0
|
|
|
|
}, {
|
|
|
|
.id = 0xa1
|
|
|
|
}, {
|
|
|
|
.id = 0xa2
|
|
|
|
}, {
|
|
|
|
.id = 0xa3
|
|
|
|
}, {
|
|
|
|
.id = 0xa4
|
|
|
|
}, {
|
|
|
|
.id = 0xa5
|
|
|
|
}, {
|
|
|
|
.id = 0xa6
|
|
|
|
}, {
|
|
|
|
.id = 0xa7
|
|
|
|
},
|
|
|
|
/* Terminator */
|
|
|
|
{
|
|
|
|
.id = 0x00,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2012-11-20 02:23:04 +08:00
|
|
|
static int ab3100_probe(struct i2c_client *client,
|
2010-08-20 16:26:56 +08:00
|
|
|
const struct i2c_device_id *id)
|
2009-05-22 05:17:06 +08:00
|
|
|
{
|
|
|
|
struct ab3100 *ab3100;
|
2009-09-09 17:31:00 +08:00
|
|
|
struct ab3100_platform_data *ab3100_plf_data =
|
|
|
|
client->dev.platform_data;
|
2009-05-22 05:17:06 +08:00
|
|
|
int err;
|
|
|
|
int i;
|
|
|
|
|
2012-06-13 02:26:58 +08:00
|
|
|
ab3100 = devm_kzalloc(&client->dev, sizeof(struct ab3100), GFP_KERNEL);
|
2009-05-22 05:17:06 +08:00
|
|
|
if (!ab3100) {
|
|
|
|
dev_err(&client->dev, "could not allocate AB3100 device\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize data structure */
|
|
|
|
mutex_init(&ab3100->access_mutex);
|
|
|
|
BLOCKING_INIT_NOTIFIER_HEAD(&ab3100->event_subscribers);
|
|
|
|
|
|
|
|
ab3100->i2c_client = client;
|
|
|
|
ab3100->dev = &ab3100->i2c_client->dev;
|
|
|
|
|
|
|
|
i2c_set_clientdata(client, ab3100);
|
|
|
|
|
|
|
|
/* Read chip ID register */
|
2009-08-13 17:49:23 +08:00
|
|
|
err = ab3100_get_register_interruptible(ab3100, AB3100_CID,
|
|
|
|
&ab3100->chip_id);
|
2009-05-22 05:17:06 +08:00
|
|
|
if (err) {
|
|
|
|
dev_err(&client->dev,
|
|
|
|
"could not communicate with the AB3100 analog "
|
|
|
|
"baseband chip\n");
|
|
|
|
goto exit_no_detect;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; ids[i].id != 0x0; i++) {
|
|
|
|
if (ids[i].id == ab3100->chip_id) {
|
|
|
|
if (ids[i].name != NULL) {
|
|
|
|
snprintf(&ab3100->chip_name[0],
|
|
|
|
sizeof(ab3100->chip_name) - 1,
|
|
|
|
"AB3100 %s",
|
|
|
|
ids[i].name);
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
dev_err(&client->dev,
|
|
|
|
"AB3000 is not supported\n");
|
|
|
|
goto exit_no_detect;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ids[i].id == 0x0) {
|
|
|
|
dev_err(&client->dev, "unknown analog baseband chip id: 0x%x\n",
|
|
|
|
ab3100->chip_id);
|
|
|
|
dev_err(&client->dev, "accepting it anyway. Please update "
|
|
|
|
"the driver.\n");
|
|
|
|
goto exit_no_detect;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_info(&client->dev, "Detected chip: %s\n",
|
|
|
|
&ab3100->chip_name[0]);
|
|
|
|
|
|
|
|
/* Attach a second dummy i2c_client to the test register address */
|
|
|
|
ab3100->testreg_client = i2c_new_dummy(client->adapter,
|
2012-06-13 02:26:58 +08:00
|
|
|
client->addr + 1);
|
2009-05-22 05:17:06 +08:00
|
|
|
if (!ab3100->testreg_client) {
|
|
|
|
err = -ENOMEM;
|
|
|
|
goto exit_no_testreg_client;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ab3100_setup(ab3100);
|
|
|
|
if (err)
|
|
|
|
goto exit_no_setup;
|
|
|
|
|
2012-06-13 02:26:58 +08:00
|
|
|
err = devm_request_threaded_irq(&client->dev,
|
|
|
|
client->irq, NULL, ab3100_irq_handler,
|
|
|
|
IRQF_ONESHOT, "ab3100-core", ab3100);
|
2009-05-22 05:17:06 +08:00
|
|
|
if (err)
|
|
|
|
goto exit_no_irq;
|
|
|
|
|
2010-05-02 00:26:20 +08:00
|
|
|
err = abx500_register_ops(&client->dev, &ab3100_ops);
|
|
|
|
if (err)
|
|
|
|
goto exit_no_ops;
|
|
|
|
|
2010-08-20 16:27:12 +08:00
|
|
|
/* Set up and register the platform devices. */
|
2011-04-06 06:41:43 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(ab3100_devs); i++) {
|
|
|
|
ab3100_devs[i].platform_data = ab3100_plf_data;
|
|
|
|
ab3100_devs[i].pdata_size = sizeof(struct ab3100_platform_data);
|
|
|
|
}
|
2009-05-22 05:17:06 +08:00
|
|
|
|
2010-08-20 16:27:12 +08:00
|
|
|
err = mfd_add_devices(&client->dev, 0, ab3100_devs,
|
2012-09-11 15:16:36 +08:00
|
|
|
ARRAY_SIZE(ab3100_devs), NULL, 0, NULL);
|
2009-05-22 05:17:06 +08:00
|
|
|
|
|
|
|
ab3100_setup_debugfs(ab3100);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
2010-05-02 00:26:20 +08:00
|
|
|
exit_no_ops:
|
2009-05-22 05:17:06 +08:00
|
|
|
exit_no_irq:
|
|
|
|
exit_no_setup:
|
|
|
|
i2c_unregister_device(ab3100->testreg_client);
|
|
|
|
exit_no_testreg_client:
|
|
|
|
exit_no_detect:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2012-11-20 02:26:01 +08:00
|
|
|
static int ab3100_remove(struct i2c_client *client)
|
2009-05-22 05:17:06 +08:00
|
|
|
{
|
|
|
|
struct ab3100 *ab3100 = i2c_get_clientdata(client);
|
|
|
|
|
|
|
|
/* Unregister subdevices */
|
2010-08-20 16:27:12 +08:00
|
|
|
mfd_remove_devices(&client->dev);
|
2009-05-22 05:17:06 +08:00
|
|
|
ab3100_remove_debugfs();
|
|
|
|
i2c_unregister_device(ab3100->testreg_client);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct i2c_device_id ab3100_id[] = {
|
2009-10-05 04:53:44 +08:00
|
|
|
{ "ab3100", 0 },
|
2009-05-22 05:17:06 +08:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, ab3100_id);
|
|
|
|
|
|
|
|
static struct i2c_driver ab3100_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "ab3100",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
},
|
|
|
|
.id_table = ab3100_id,
|
|
|
|
.probe = ab3100_probe,
|
2012-11-20 02:20:24 +08:00
|
|
|
.remove = ab3100_remove,
|
2009-05-22 05:17:06 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int __init ab3100_i2c_init(void)
|
|
|
|
{
|
|
|
|
return i2c_add_driver(&ab3100_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit ab3100_i2c_exit(void)
|
|
|
|
{
|
|
|
|
i2c_del_driver(&ab3100_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
subsys_initcall(ab3100_i2c_init);
|
|
|
|
module_exit(ab3100_i2c_exit);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>");
|
|
|
|
MODULE_DESCRIPTION("AB3100 core driver");
|
|
|
|
MODULE_LICENSE("GPL");
|