blob: def353ca9c1a853967364cb2f17d2b48f40c5f6e [file] [log] [blame]
/*
* tps61050.c - tps61050 flash/torch kernel driver
*
* Copyright (C) 2011 NVIDIA Corporation.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307, USA
*/
/* Implementation
* --------------
* The board level details about the device need to be provided in the board
* file with the tps61050_platform_data structure.
* Standard among NVC kernel drivers in this structure is:
* .cfg = Use the NVC_CFG_ defines that are in nvc_torch.h.
* Descriptions of the configuration options are with the defines.
* This value is typically 0.
* .num = The number of the instance of the device. This should start at 1 and
* and increment for each device on the board. This number will be
* appended to the MISC driver name, Example: /dev/tps61050.1
* .sync = If there is a need to synchronize two devices, then this value is
* the number of the device instance this device is allowed to sync to.
* This is typically used for stereo applications.
* .dev_name = The MISC driver name the device registers as. If not used,
* then the part number of the device is used for the driver name.
* If using the NVC user driver then use the name found in this
* driver under _default_pdata.
*
* The following is specific to NVC kernel flash/torch drivers:
* .pinstate = a pointer to the nvc_torch_pin_state structure. This
* structure gives the details of which VI GPIO to use to trigger
* the flash. The mask tells which pin and the values is the
* level. For example, if VI GPIO pin 6 is used, then
* .mask = 0x0040
* .values = 0x0040
* If VI GPIO pin 0 is used, then
* .mask = 0x0001
* .values = 0x0001
* This is typically just one pin but there is some legacy
* here that insinuates more than one pin can be used.
* When the flash level is set, then the driver will return the
* value in values. When the flash level is off, the driver will
* return 0 for the values to deassert the signal.
* If a VI GPIO is not used, then the mask and values must be set
* to 0. The flash may then be triggered via I2C instead.
* However, a VI GPIO is strongly encouraged since it allows
* tighter timing with the picture taken as well as reduced power
* by asserting the trigger signal for only when needed.
* .max_amp_torch = Is the maximum torch value allowed. The value is 0 to
* _MAX_TORCH_LEVEL. This is to allow a limit to the amount
* of amps used. If left blank then _MAX_TORCH_LEVEL will be
* used.
* .max_amp_flash = Is the maximum flash value allowed. The value is 0 to
* _MAX_FLASH_LEVEL. This is to allow a limit to the amount
* of amps used. If left blank then _MAX_FLASH_LEVEL will be
* used.
*
* The following is specific to only this NVC kernel flash/torch driver:
* N/A
*
* Power Requirements
* The board power file must contain the following labels for the power
* regulator(s) of this device:
* "vdd_i2c" = the power regulator for the I2C power.
* Note that this device is typically connected directly to the battery rail
* and does not need a source power regulator (vdd).
*
* The above values should be all that is needed to use the device with this
* driver. Modifications of this driver should not be needed.
*/
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/list.h>
#include <linux/regulator/consumer.h>
#include <media/nvc.h>
#include <media/tps61050.h>
#define TPS61050_REG0 0x00
#define TPS61050_REG1 0x01
#define TPS61050_REG2 0x02
#define TPS61050_REG3 0x03
#define tps61050_flash_cap_size (sizeof(tps61050_flash_cap.numberoflevels) \
+ (sizeof(tps61050_flash_cap.levels[0]) \
* (TPS61050_MAX_FLASH_LEVEL + 1)))
#define tps61050_torch_cap_size (sizeof(tps61050_torch_cap.numberoflevels) \
+ (sizeof(tps61050_torch_cap.guidenum[0]) \
* (TPS61050_MAX_TORCH_LEVEL + 1)))
static struct nvc_torch_flash_capabilities tps61050_flash_cap = {
TPS61050_MAX_FLASH_LEVEL + 1,
{
{ 0, 0xFFFFFFFF, 0 },
{ 150, 558, 2 },
{ 200, 558, 2 },
{ 300, 558, 2 },
{ 400, 558, 2 },
{ 500, 558, 2 },
{ 700, 558, 2 },
{ 900, 558, 2 },
{ 900, 558, 2 }
}
};
static struct nvc_torch_torch_capabilities tps61050_torch_cap = {
TPS61050_MAX_TORCH_LEVEL + 1,
{
0,
50,
75,
100,
150,
200,
491,
491
}
};
struct tps61050_info {
atomic_t in_use;
struct i2c_client *i2c_client;
struct tps61050_platform_data *pdata;
struct miscdevice miscdev;
struct list_head list;
int pwr_api;
int pwr_dev;
struct nvc_regulator vreg_i2c;
u8 s_mode;
struct tps61050_info *s_info;
};
static struct nvc_torch_pin_state tps61050_default_pinstate = {
.mask = 0x0000,
.values = 0x0000,
};
static struct tps61050_platform_data tps61050_default_pdata = {
.cfg = 0,
.num = 0,
.sync = 0,
.dev_name = "torch",
.pinstate = &tps61050_default_pinstate,
.max_amp_torch = TPS61050_MAX_TORCH_LEVEL,
.max_amp_flash = TPS61050_MAX_FLASH_LEVEL,
};
static LIST_HEAD(tps61050_info_list);
static DEFINE_SPINLOCK(tps61050_spinlock);
static int tps61050_i2c_rd(struct tps61050_info *info, u8 reg, u8 *val)
{
struct i2c_msg msg[2];
u8 buf[2];
buf[0] = reg;
msg[0].addr = info->i2c_client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = &buf[0];
msg[1].addr = info->i2c_client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = 1;
msg[1].buf = &buf[1];
*val = 0;
if (i2c_transfer(info->i2c_client->adapter, msg, 2) != 2)
return -EIO;
*val = buf[1];
return 0;
}
static int tps61050_i2c_wr(struct tps61050_info *info, u8 reg, u8 val)
{
struct i2c_msg msg;
u8 buf[2];
buf[0] = reg;
buf[1] = val;
msg.addr = info->i2c_client->addr;
msg.flags = 0;
msg.len = 2;
msg.buf = &buf[0];
if (i2c_transfer(info->i2c_client->adapter, &msg, 1) != 1)
return -EIO;
return 0;
}
static void tps61050_pm_regulator_put(struct nvc_regulator *sreg)
{
regulator_put(sreg->vreg);
sreg->vreg = NULL;
}
static int tps61050_pm_regulator_get(struct tps61050_info *info,
struct nvc_regulator *sreg,
char vreg_name[])
{
int err = 0;
sreg->vreg_flag = 0;
sreg->vreg = regulator_get(&info->i2c_client->dev, vreg_name);
if (IS_ERR_OR_NULL(sreg->vreg)) {
dev_err(&info->i2c_client->dev,
"%s err for regulator: %s err: %d\n",
__func__, vreg_name, (int)sreg->vreg);
err = PTR_ERR(sreg->vreg);
sreg->vreg = NULL;
} else {
sreg->vreg_name = vreg_name;
dev_dbg(&info->i2c_client->dev,
"%s vreg_name: %s\n",
__func__, sreg->vreg_name);
}
return err;
}
static int tps61050_pm_regulator_en(struct tps61050_info *info,
struct nvc_regulator *sreg)
{
int err = 0;
if (!sreg->vreg_flag && (sreg->vreg != NULL)) {
err = regulator_enable(sreg->vreg);
if (!err) {
dev_dbg(&info->i2c_client->dev,
"%s vreg_name: %s\n",
__func__, sreg->vreg_name);
sreg->vreg_flag = 1;
err = 1; /* flag regulator state change */
mdelay(5); /* device powerup delay */
} else {
dev_err(&info->i2c_client->dev,
"%s err, regulator: %s\n",
__func__, sreg->vreg_name);
}
}
return err;
}
static int tps61050_pm_regulator_dis(struct tps61050_info *info,
struct nvc_regulator *sreg)
{
int err = 0;
if (sreg->vreg_flag && (sreg->vreg != NULL)) {
err = regulator_disable(sreg->vreg);
if (!err)
dev_dbg(&info->i2c_client->dev,
"%s vreg_name: %s\n",
__func__, sreg->vreg_name);
else
dev_err(&info->i2c_client->dev,
"%s err, regulator: %s\n",
__func__, sreg->vreg_name);
}
sreg->vreg_flag = 0;
return err;
}
static int tps61050_pm_wr(struct tps61050_info *info, int pwr)
{
int err = 0;
u8 reg;
if (pwr == info->pwr_dev)
return 0;
switch (pwr) {
case NVC_PWR_OFF:
if ((info->pdata->cfg & NVC_CFG_OFF2STDBY) ||
(info->pdata->cfg & NVC_CFG_BOOT_INIT)) {
pwr = NVC_PWR_STDBY;
} else {
err = tps61050_pm_regulator_en(info, &info->vreg_i2c);
err |= tps61050_i2c_wr(info, TPS61050_REG0, 0x00);
err |= tps61050_i2c_wr(info, TPS61050_REG1, 0x00);
err |= tps61050_pm_regulator_dis(info, &info->vreg_i2c);
break;
}
case NVC_PWR_STDBY_OFF:
if ((info->pdata->cfg & NVC_CFG_OFF2STDBY) ||
(info->pdata->cfg & NVC_CFG_BOOT_INIT)) {
pwr = NVC_PWR_STDBY;
} else {
err = tps61050_pm_regulator_en(info, &info->vreg_i2c);
err |= tps61050_i2c_wr(info, TPS61050_REG0, 0x00);
err |= tps61050_i2c_wr(info, TPS61050_REG1, 0x00);
break;
}
case NVC_PWR_STDBY:
err = tps61050_pm_regulator_en(info, &info->vreg_i2c);
err |= tps61050_i2c_rd(info, TPS61050_REG0, &reg);
reg &= 0x3F; /* 7:6 = mode */
err |= tps61050_i2c_wr(info, TPS61050_REG0, reg);
break;
case NVC_PWR_COMM:
case NVC_PWR_ON:
err = tps61050_pm_regulator_en(info, &info->vreg_i2c);
break;
default:
err = -EINVAL;
break;
}
if (err < 0) {
dev_err(&info->i2c_client->dev, "%s error\n", __func__);
pwr = NVC_PWR_ERR;
}
info->pwr_dev = pwr;
if (err > 0)
return 0;
return err;
}
static int tps61050_pm_wr_s(struct tps61050_info *info, int pwr)
{
int err1 = 0;
int err2 = 0;
if ((info->s_mode == NVC_SYNC_OFF) ||
(info->s_mode == NVC_SYNC_MASTER) ||
(info->s_mode == NVC_SYNC_STEREO))
err1 = tps61050_pm_wr(info, pwr);
if ((info->s_mode == NVC_SYNC_SLAVE) ||
(info->s_mode == NVC_SYNC_STEREO))
err2 = tps61050_pm_wr(info->s_info, pwr);
return err1 | err2;
}
static int tps61050_pm_api_wr(struct tps61050_info *info, int pwr)
{
int err = 0;
if (!pwr || (pwr > NVC_PWR_ON))
return 0;
if (pwr > info->pwr_dev)
err = tps61050_pm_wr_s(info, pwr);
if (!err)
info->pwr_api = pwr;
else
info->pwr_api = NVC_PWR_ERR;
if (info->pdata->cfg & NVC_CFG_NOERR)
return 0;
return err;
}
static int tps61050_pm_dev_wr(struct tps61050_info *info, int pwr)
{
if (pwr < info->pwr_api)
pwr = info->pwr_api;
return tps61050_pm_wr(info, pwr);
}
static void tps61050_pm_exit(struct tps61050_info *info)
{
tps61050_pm_wr_s(info, NVC_PWR_OFF);
tps61050_pm_regulator_put(&info->vreg_i2c);
}
static void tps61050_pm_init(struct tps61050_info *info)
{
tps61050_pm_regulator_get(info, &info->vreg_i2c, "vdd_i2c");
}
struct tps61050_reg_init {
u8 mask;
u8 val;
};
static struct tps61050_reg_init tps61050_reg_init_id[] = {
{0xC0, 0x00},
{0xC0, 0x00},
{0x87, 0x00},
{0xFF, 0xD1},
};
static int tps61050_dev_id(struct tps61050_info *info)
{
u8 reg;
u8 i;
int err;
tps61050_pm_dev_wr(info, NVC_PWR_COMM);
/* There isn't a device ID so we just check that all the registers
* equal their startup defaults.
*/
for (i = TPS61050_REG0; i <= TPS61050_REG3; i++) {
err = tps61050_i2c_rd(info, i, &reg);
if (err) {
break;
} else {
reg &= tps61050_reg_init_id[i].mask;
if (reg != tps61050_reg_init_id[i].val) {
err = -ENODEV;
break;
}
}
}
tps61050_pm_dev_wr(info, NVC_PWR_OFF);
return err;
}
static int tps61050_param_rd(struct tps61050_info *info, long arg)
{
struct nvc_param params;
struct nvc_torch_pin_state pinstate;
const void *data_ptr;
u8 reg;
u32 data_size = 0;
int err;
if (copy_from_user(&params,
(const void __user *)arg,
sizeof(struct nvc_param))) {
dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n",
__func__, __LINE__);
return -EINVAL;
}
if (info->s_mode == NVC_SYNC_SLAVE)
info = info->s_info;
switch (params.param) {
case NVC_PARAM_FLASH_CAPS:
dev_dbg(&info->i2c_client->dev, "%s FLASH_CAPS\n", __func__);
data_ptr = &tps61050_flash_cap;
data_size = tps61050_flash_cap_size;
break;
case NVC_PARAM_FLASH_LEVEL:
tps61050_pm_dev_wr(info, NVC_PWR_COMM);
err = tps61050_i2c_rd(info, TPS61050_REG1, &reg);
tps61050_pm_dev_wr(info, NVC_PWR_OFF);
if (err < 0)
return err;
if (reg & 0x80) { /* 7:7 flash on/off */
reg &= 0x07; /* 2:0 flash setting */
reg++; /* flash setting +1 if flash on */
} else {
reg = 0; /* flash is off */
}
dev_dbg(&info->i2c_client->dev, "%s FLASH_LEVEL: %u\n",
__func__,
(unsigned)tps61050_flash_cap.levels[reg].guidenum);
data_ptr = &tps61050_flash_cap.levels[reg].guidenum;
data_size = sizeof(tps61050_flash_cap.levels[reg].guidenum);
break;
case NVC_PARAM_TORCH_CAPS:
dev_dbg(&info->i2c_client->dev, "%s TORCH_CAPS\n", __func__);
data_ptr = &tps61050_torch_cap;
data_size = tps61050_torch_cap_size;
break;
case NVC_PARAM_TORCH_LEVEL:
tps61050_pm_dev_wr(info, NVC_PWR_COMM);
err = tps61050_i2c_rd(info, TPS61050_REG0, &reg);
tps61050_pm_dev_wr(info, NVC_PWR_OFF);
if (err < 0)
return err;
reg &= 0x07;
dev_dbg(&info->i2c_client->dev, "%s TORCH_LEVEL: %u\n",
__func__,
(unsigned)tps61050_torch_cap.guidenum[reg]);
data_ptr = &tps61050_torch_cap.guidenum[reg];
data_size = sizeof(tps61050_torch_cap.guidenum[reg]);
break;
case NVC_PARAM_FLASH_PIN_STATE:
pinstate.mask = info->pdata->pinstate->mask;
tps61050_pm_dev_wr(info, NVC_PWR_COMM);
err = tps61050_i2c_rd(info, TPS61050_REG1, &reg);
tps61050_pm_dev_wr(info, NVC_PWR_OFF);
if (err < 0)
return err;
reg &= 0x80; /* 7:7=flash enable */
if (reg)
/* assert strobe */
pinstate.values = info->pdata->pinstate->values;
else
pinstate.values = 0; /* deassert strobe */
dev_dbg(&info->i2c_client->dev, "%s FLASH_PIN_STATE: %x&%x\n",
__func__, pinstate.mask, pinstate.values);
data_ptr = &pinstate;
data_size = sizeof(struct nvc_torch_pin_state);
break;
case NVC_PARAM_STEREO:
dev_dbg(&info->i2c_client->dev, "%s STEREO: %d\n",
__func__, (int)info->s_mode);
data_ptr = &info->s_mode;
data_size = sizeof(info->s_mode);
break;
default:
dev_err(&info->i2c_client->dev,
"%s unsupported parameter: %d\n",
__func__, params.param);
return -EINVAL;
}
if (params.sizeofvalue < data_size) {
dev_err(&info->i2c_client->dev,
"%s data size mismatch %d != %d\n",
__func__, params.sizeofvalue, data_size);
return -EINVAL;
}
if (copy_to_user((void __user *)params.p_value,
data_ptr,
data_size)) {
dev_err(&info->i2c_client->dev,
"%s copy_to_user err line %d\n",
__func__, __LINE__);
return -EFAULT;
}
return 0;
}
static int tps61050_param_wr_s(struct tps61050_info *info,
struct nvc_param *params,
u8 val)
{
u8 reg;
int err = 0;
/*
* 7:6 flash/torch mode
* 0 0 = off (power save)
* 0 1 = torch only (torch power is 2:0 REG0 where 0 = off)
* 1 0 = flash and torch (flash power is 2:0 REG1 (0 is a power level))
* 1 1 = N/A
* Note that 7:6 of REG0 and REG1 are shadowed with each other.
* In the code below we want to turn on/off one
* without affecting the other.
*/
switch (params->param) {
case NVC_PARAM_FLASH_LEVEL:
dev_dbg(&info->i2c_client->dev, "%s FLASH_LEVEL: %d\n",
__func__, val);
tps61050_pm_dev_wr(info, NVC_PWR_ON);
if (val) {
val--;
if (val > tps61050_default_pdata.max_amp_flash)
val = tps61050_default_pdata.max_amp_flash;
/* Amp limit values are in the board-sensors file. */
if (info->pdata->max_amp_flash &&
(val > info->pdata->max_amp_flash))
val = info->pdata->max_amp_flash;
val |= 0x80; /* 7:7=flash mode */
} else {
err = tps61050_i2c_rd(info, TPS61050_REG0, &reg);
if (reg & 0x07) /* 2:0=torch setting */
val = 0x40; /* 6:6 enable just torch */
}
err |= tps61050_i2c_wr(info, TPS61050_REG1, val);
val &= 0xC0; /* 7:6=flash/torch mode */
if (!val) /* turn pwr off if no flash && no pwr_api */
tps61050_pm_dev_wr(info, NVC_PWR_OFF);
return err;
case NVC_PARAM_TORCH_LEVEL:
dev_dbg(&info->i2c_client->dev, "%s TORCH_LEVEL: %d\n",
__func__, val);
tps61050_pm_dev_wr(info, NVC_PWR_ON);
err = tps61050_i2c_rd(info, TPS61050_REG1, &reg);
reg &= 0x80; /* 7:7=flash */
if (val) {
if (val > tps61050_default_pdata.max_amp_torch)
val = tps61050_default_pdata.max_amp_torch;
/* Amp limit values are in the board-sensors file. */
if (info->pdata->max_amp_torch &&
(val > info->pdata->max_amp_torch))
val = info->pdata->max_amp_torch;
if (!reg) /* test if flash/torch off */
val |= (0x40); /* 6:6=torch only mode */
} else {
val |= reg;
}
err |= tps61050_i2c_wr(info, TPS61050_REG0, val);
val &= 0xC0; /* 7:6=mode */
if (!val) /* turn pwr off if no torch && no pwr_api */
tps61050_pm_dev_wr(info, NVC_PWR_OFF);
return err;
case NVC_PARAM_FLASH_PIN_STATE:
dev_dbg(&info->i2c_client->dev, "%s FLASH_PIN_STATE: %d\n",
__func__, val);
if (val)
val = 0x08; /* 3:3=soft trigger */
err = tps61050_i2c_rd(info, TPS61050_REG1, &reg);
val |= reg;
err |= tps61050_i2c_wr(info, TPS61050_REG1, val);
return err;
default:
dev_err(&info->i2c_client->dev,
"%s unsupported parameter: %d\n",
__func__, params->param);
return -EINVAL;
}
}
static int tps61050_param_wr(struct tps61050_info *info, long arg)
{
struct nvc_param params;
u8 val;
int err = 0;
if (copy_from_user(&params, (const void __user *)arg,
sizeof(struct nvc_param))) {
dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n",
__func__, __LINE__);
return -EINVAL;
}
if (copy_from_user(&val, (const void __user *)params.p_value,
sizeof(val))) {
dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n",
__func__, __LINE__);
return -EINVAL;
}
/* parameters independent of sync mode */
switch (params.param) {
case NVC_PARAM_STEREO:
dev_dbg(&info->i2c_client->dev, "%s STEREO: %d\n",
__func__, (int)val);
if (val == info->s_mode)
return 0;
switch (val) {
case NVC_SYNC_OFF:
info->s_mode = val;
if (info->s_info != NULL) {
info->s_info->s_mode = val;
tps61050_pm_wr(info->s_info, NVC_PWR_OFF);
}
break;
case NVC_SYNC_MASTER:
info->s_mode = val;
if (info->s_info != NULL)
info->s_info->s_mode = val;
break;
case NVC_SYNC_SLAVE:
case NVC_SYNC_STEREO:
if (info->s_info != NULL) {
/* sync power */
info->s_info->pwr_api = info->pwr_api;
err = tps61050_pm_wr(info->s_info,
info->pwr_dev);
if (!err) {
info->s_mode = val;
info->s_info->s_mode = val;
} else {
tps61050_pm_wr(info->s_info,
NVC_PWR_OFF);
err = -EIO;
}
} else {
err = -EINVAL;
}
break;
default:
err = -EINVAL;
}
if (info->pdata->cfg & NVC_CFG_NOERR)
return 0;
return err;
default:
/* parameters dependent on sync mode */
switch (info->s_mode) {
case NVC_SYNC_OFF:
case NVC_SYNC_MASTER:
return tps61050_param_wr_s(info, &params, val);
case NVC_SYNC_SLAVE:
return tps61050_param_wr_s(info->s_info,
&params,
val);
case NVC_SYNC_STEREO:
err = tps61050_param_wr_s(info, &params, val);
if (!(info->pdata->cfg & NVC_CFG_SYNC_I2C_MUX))
err |= tps61050_param_wr_s(info->s_info,
&params,
val);
return err;
default:
dev_err(&info->i2c_client->dev, "%s %d internal err\n",
__func__, __LINE__);
return -EINVAL;
}
}
}
static long tps61050_ioctl(struct file *file,
unsigned int cmd,
unsigned long arg)
{
struct tps61050_info *info = file->private_data;
int pwr;
switch (cmd) {
case NVC_IOCTL_PARAM_WR:
return tps61050_param_wr(info, arg);
case NVC_IOCTL_PARAM_RD:
return tps61050_param_rd(info, arg);
case NVC_IOCTL_PWR_WR:
/* This is a Guaranteed Level of Service (GLOS) call */
pwr = (int)arg * 2;
dev_dbg(&info->i2c_client->dev, "%s PWR_WR: %d\n",
__func__, pwr);
return tps61050_pm_api_wr(info, pwr);
case NVC_IOCTL_PWR_RD:
if (info->s_mode == NVC_SYNC_SLAVE)
pwr = info->s_info->pwr_api / 2;
else
pwr = info->pwr_api / 2;
dev_dbg(&info->i2c_client->dev, "%s PWR_RD: %d\n",
__func__, pwr);
if (copy_to_user((void __user *)arg, (const void *)&pwr,
sizeof(pwr))) {
dev_err(&info->i2c_client->dev,
"%s copy_to_user err line %d\n",
__func__, __LINE__);
return -EFAULT;
}
return 0;
default:
dev_err(&info->i2c_client->dev, "%s unsupported ioctl: %x\n",
__func__, cmd);
return -EINVAL;
}
}
static int tps61050_sync_en(int dev1, int dev2)
{
struct tps61050_info *sync1 = NULL;
struct tps61050_info *sync2 = NULL;
struct tps61050_info *pos = NULL;
rcu_read_lock();
list_for_each_entry_rcu(pos, &tps61050_info_list, list) {
if (pos->pdata->num == dev1) {
sync1 = pos;
break;
}
}
pos = NULL;
list_for_each_entry_rcu(pos, &tps61050_info_list, list) {
if (pos->pdata->num == dev2) {
sync2 = pos;
break;
}
}
rcu_read_unlock();
if (sync1 != NULL)
sync1->s_info = NULL;
if (sync2 != NULL)
sync2->s_info = NULL;
if (!dev1 && !dev2)
return 0; /* no err if default instance 0's used */
if (dev1 == dev2)
return -EINVAL; /* err if sync instance is itself */
if ((sync1 != NULL) && (sync2 != NULL)) {
sync1->s_info = sync2;
sync2->s_info = sync1;
}
return 0;
}
static int tps61050_sync_dis(struct tps61050_info *info)
{
if (info->s_info != NULL) {
info->s_info->s_mode = 0;
info->s_info->s_info = NULL;
info->s_mode = 0;
info->s_info = NULL;
return 0;
}
return -EINVAL;
}
static int tps61050_open(struct inode *inode, struct file *file)
{
struct tps61050_info *info = NULL;
struct tps61050_info *pos = NULL;
int err;
rcu_read_lock();
list_for_each_entry_rcu(pos, &tps61050_info_list, list) {
if (pos->miscdev.minor == iminor(inode)) {
info = pos;
break;
}
}
rcu_read_unlock();
if (!info)
return -ENODEV;
err = tps61050_sync_en(info->pdata->num, info->pdata->sync);
if (err == -EINVAL)
dev_err(&info->i2c_client->dev,
"%s err: invalid num (%u) and sync (%u) instance\n",
__func__, info->pdata->num, info->pdata->sync);
if (atomic_xchg(&info->in_use, 1))
return -EBUSY;
if (info->s_info != NULL) {
if (atomic_xchg(&info->s_info->in_use, 1))
return -EBUSY;
}
file->private_data = info;
dev_dbg(&info->i2c_client->dev, "%s\n", __func__);
return 0;
}
static int tps61050_release(struct inode *inode, struct file *file)
{
struct tps61050_info *info = file->private_data;
dev_dbg(&info->i2c_client->dev, "%s\n", __func__);
tps61050_pm_wr_s(info, NVC_PWR_OFF);
file->private_data = NULL;
WARN_ON(!atomic_xchg(&info->in_use, 0));
if (info->s_info != NULL)
WARN_ON(!atomic_xchg(&info->s_info->in_use, 0));
tps61050_sync_dis(info);
return 0;
}
static const struct file_operations tps61050_fileops = {
.owner = THIS_MODULE,
.open = tps61050_open,
.unlocked_ioctl = tps61050_ioctl,
.release = tps61050_release,
};
static void tps61050_del(struct tps61050_info *info)
{
tps61050_pm_exit(info);
tps61050_sync_dis(info);
spin_lock(&tps61050_spinlock);
list_del_rcu(&info->list);
spin_unlock(&tps61050_spinlock);
synchronize_rcu();
}
static int tps61050_remove(struct i2c_client *client)
{
struct tps61050_info *info = i2c_get_clientdata(client);
dev_dbg(&info->i2c_client->dev, "%s\n", __func__);
misc_deregister(&info->miscdev);
tps61050_del(info);
return 0;
}
static int tps61050_probe(
struct i2c_client *client,
const struct i2c_device_id *id)
{
struct tps61050_info *info;
char dname[16];
int err;
dev_dbg(&client->dev, "%s\n", __func__);
info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL);
if (info == NULL) {
dev_err(&client->dev, "%s: kzalloc error\n", __func__);
return -ENOMEM;
}
info->i2c_client = client;
if (client->dev.platform_data) {
info->pdata = client->dev.platform_data;
} else {
info->pdata = &tps61050_default_pdata;
dev_dbg(&client->dev,
"%s No platform data. Using defaults.\n",
__func__);
}
i2c_set_clientdata(client, info);
INIT_LIST_HEAD(&info->list);
spin_lock(&tps61050_spinlock);
list_add_rcu(&info->list, &tps61050_info_list);
spin_unlock(&tps61050_spinlock);
tps61050_pm_init(info);
err = tps61050_dev_id(info);
if (err < 0) {
dev_err(&client->dev, "%s device not found\n", __func__);
if (info->pdata->cfg & NVC_CFG_NODEV) {
tps61050_del(info);
return -ENODEV;
}
} else {
dev_dbg(&client->dev, "%s device found\n", __func__);
}
if (info->pdata->dev_name != 0)
strcpy(dname, info->pdata->dev_name);
else
strcpy(dname, "tps61050");
if (info->pdata->num)
snprintf(dname, sizeof(dname), "%s.%u",
dname, info->pdata->num);
info->miscdev.name = dname;
info->miscdev.fops = &tps61050_fileops;
info->miscdev.minor = MISC_DYNAMIC_MINOR;
if (misc_register(&info->miscdev)) {
dev_err(&client->dev, "%s unable to register misc device %s\n",
__func__, dname);
tps61050_del(info);
return -ENODEV;
}
return 0;
}
static const struct i2c_device_id tps61050_id[] = {
{ "tps61050", 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, tps61050_id);
static struct i2c_driver tps61050_i2c_driver = {
.driver = {
.name = "tps61050",
.owner = THIS_MODULE,
},
.id_table = tps61050_id,
.probe = tps61050_probe,
.remove = tps61050_remove,
};
static int __init tps61050_init(void)
{
return i2c_add_driver(&tps61050_i2c_driver);
}
static void __exit tps61050_exit(void)
{
i2c_del_driver(&tps61050_i2c_driver);
}
module_init(tps61050_init);
module_exit(tps61050_exit);
MODULE_LICENSE("GPL");