/*
* Copyright 2008-2011 MTC, Inc. All Rights Reserved.
*/
/*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
/*!
* @file drivers/hwmon/stk2211.c
*
* @brief STK2211 light sensor Driver
*
* @ingroup
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/regulator/consumer.h>
#include <mach/hardware.h>
enum stk2211_width {
STK2211_WIDTH_12=0,
STK2211_WIDTH_8,
STK2211_WIDTH_4,
};
enum stk2211_gain {
STK2211_GAIN_4096=0,
STK2211_GAIN_2048,
STK2211_GAIN_1024,
STK2211_GAIN_512,
};
enum stk2211_mode {
STK2211_MODE_DIODE1 = 0,
STK2211_MODE_DIODE2,
STK2211_MODE_DIODE1_2,
};
struct stk2211_param {
enum stk2211_width width;
enum stk2211_gain gain;
enum stk2211_mode mode;
};
/* bit definition for STK2211_CMD reg */
#define ENABLE 7
#define ADCPD 6
#define TIMEING_MODE 5
#define MODE 2
#define WIDTH 0
/* bit definition for STK2211_CTRL reg */
#define INT_FLAG 5
#define GAIN 2
#define INT_PERSIST 0
enum stk2211_reg {
STK2211_CMD_0 = 0,
STK2211_CMD_1,
STK2211_CMD_2,
STK2211_CMD_3,
};
/* default configure for STK2211 */
#define STK2211_WIDTH_DEFAULT STK2211_WIDTH_12
#define STK2211_GAIN_DEFAULT STK2211_GAIN_4096
#define STK2211_MODE_DEFAULT STK2211_MODE_DIODE1
/* range table for different GAIN settings */
int range[4] = { 4906, 2048, 1024, 521 };
/* width table for different WIDTH settings */
int width[4] = { 16, 1, 256, 16 };
struct stk2211_data {
struct i2c_client *client;
struct device *hwmon_dev;
struct regulator *vdd_reg;
struct stk2211_param param;
int lux_coeff;
unsigned char enable;
};
static struct i2c_client *stk2211_client;
/*!
* This function do the stk2211 register read.
*/
int stk2211_read(struct i2c_client *client, u8 reg)
{
return i2c_smbus_read_byte_data(client, reg);
}
/*!
* This function do the stk2211 register write.
*/
int stk2211_write(struct i2c_client *client, u8 reg, char value)
{
return i2c_smbus_write_byte_data(client, reg, value);
}
/*!
* This function do the stk2211 config and enable.
*/
static int stk2211_on(void)
{
unsigned char cmd;
int err = 0;
struct mxc_lightsensor_platform_data *stk_data;
struct stk2211_data *data = i2c_get_clientdata(stk2211_client);
printk("\n===joe==%s,%d\n",__func__,__LINE__);
if (data->enable)
goto exit;
stk_data = (struct mxc_lightsensor_platform_data *)
(stk2211_client->dev).platform_data;
/* coeff=range*100k/rext/2^n */
data->lux_coeff = range[data->param.gain] * 100 /
stk_data->rext / width[data->param.width];
if (data->vdd_reg)
regulator_enable(data->vdd_reg);
msleep(100);
cmd = data->param.gain << GAIN;
if (stk2211_write(stk2211_client, 0x01, cmd)) {
err = -ENODEV;
goto exit;
}
cmd = (data->param.width << WIDTH) | (data->param.mode << MODE) |
(1 << ENABLE);
if (stk2211_write(stk2211_client, 0x01, cmd)) {
err = -ENODEV;
goto exit;
}
data->enable = 1;
pr_info("stk2211 on\n");
return 0;
exit:
return err;
}
/*!
* This function shut down the stk2211.
*/
static int stk2211_off(void)
{
struct stk2211_data *data = i2c_get_clientdata(stk2211_client);
int cmd;
printk("\n===joe==%s,%d\n",__func__,__LINE__);
if (!data->enable)
return 0;
cmd = stk2211_read(stk2211_client, 0x02);
if (cmd < 0)
return -ENODEV;
cmd = ((cmd | (1 << ADCPD)) & (~(1 << ENABLE)));
if (stk2211_write(stk2211_client, 0x03, (char)cmd))
return -ENODEV;
if (data->vdd_reg)
regulator_disable(data->vdd_reg);
data->enable = 0;
pr_info("stk2211 off\n");
return 0;
}
/*!
* This function read the stk2211 lux registers and convert them to the lux
* value.
*
* @output buffer this param holds the lux value, when =-1, read fail
*
* @return 0
*/
static int stk2211_read_lux(void)
{
int d;
int lux;
struct stk2211_data *data = i2c_get_clientdata(stk2211_client);
stk2211_write(stk2211_client, 0x00, 0x00);
msleep(10);
d = stk2211_read(stk2211_client, 0x02);
if (d < 0)
goto err;
lux = d;
/*printk("\n===lux1 0x02=%d===\n",lux);*/
d = stk2211_read(stk2211_client, 0x03);
if (d < 0)
goto err;
/*printk("\n===lux2 0x03 =%d===\n",d );*/
/*final data 12bit */
lux = (lux << 8) + d;
/*
printk("d>>4=%d\n",(d>>4));
printk("\n===final data=%d===\n",lux);
*/
/*
if (data->param.width < STK2211_WIDTH_8)
lux = (data->lux_coeff * lux) >> 12;
else
lux = data->lux_coeff * lux;
*/
/*printk("\n===lux4=%d===\n",lux);*/
return lux;
err:
return -1;
}
static ssize_t ls_enable(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
char *endp;
int enable = simple_strtoul(buf, &endp, 0);
size_t size = endp - buf;
if (*endp && isspace(*endp))
size++;
if (size != count)
return -EINVAL;
if (enable == 1) {
if (stk2211_on())
pr_info("device open fail\n");
}
if (enable == 0) {
if (stk2211_off())
pr_info("device powerdown fail\n");
}
return count;
}
static SENSOR_DEVICE_ATTR(enable, S_IWUGO, NULL, ls_enable, 0);
static ssize_t show_lux(struct device *dev, struct device_attribute *attr,
char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", stk2211_read_lux());
}
static SENSOR_DEVICE_ATTR(lux, S_IRUGO, show_lux, NULL, 0);
static int __devinit stk2211_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *did)
{
int err = 0;
struct stk2211_data *data;
struct regulator *vdd_reg;
struct mxc_lightsensor_platform_data *stk_data;
stk_data = (struct mxc_lightsensor_platform_data *)
(client->dev).platform_data;
if (stk_data && stk_data->vdd_reg)
vdd_reg = regulator_get(&client->dev, stk_data->vdd_reg);
else
vdd_reg = NULL;
printk("===client=%s====\n",client->name);
/* check the existence of the device */
if (vdd_reg)
regulator_enable(vdd_reg);
msleep(100);
printk("===vdd_reg2 max_uV=%s====\n",stk_data->vdd_reg);
printk("===STK2211_CMD_0=%d,==client-addr=%d=\n",STK2211_CMD_0,client->addr);
/*
while (1)
{
printk("===stk2211_write=%d,==client-addr=%d=",STK2211_CMD_0,client->addr);
if (stk2211_write(client, STK2211_CMD_0, 0)) {
err = -ENODEV;
printk("===stk2211_write=%d,ENODEV=%d==client-addr=%d=",STK2211_CMD_0,ENODEV,client->addr);
}
msleep(100);
}
*/
if (stk2211_write(client, 0x01, 0x8c))
err = -ENODEV;
printk("read=%d\n",stk2211_read(client, 0x01));
msleep(100);
printk("write0=%d\n",stk2211_write(client, 0x00, 0x00));
printk("read0=%d\n",stk2211_read(client, 0x00));
msleep(100);
printk("write1=%d\n",stk2211_write(client, 0x02, 0x22));
printk("read1=%d\n",stk2211_read(client, 0x02));
msleep(100);
printk("write2=%d\n",stk2211_write(client, 0x03, 0x33));
printk("read2=%d\n",stk2211_read(client, 0x03));
/*
if (!err)
if (stk2211_read(client, 0x01))
err = -ENODEV;
*/
if (vdd_reg)
regulator_disable(vdd_reg);
if (err < 0)
goto exit1;
stk2211_client = client;
data = kzalloc(sizeof(struct stk2211_data), GFP_KERNEL);
if (data == NULL) {
err = -ENOMEM;
goto exit1;
}
printk("\n===joe2==%s,%d\n",__func__,__LINE__);
i2c_set_clientdata(client, data);
data->client = client;
//data->param.width = STK2211_WIDTH_DEFAULT;
//data->param.gain = STK2211_GAIN_DEFAULT;
//data->param.mode = STK2211_MODE_DEFAULT;
data->enable = 0;
err = device_create_file(&client->dev,
&sensor_dev_attr_enable.dev_attr);
printk("==device_create_file==err=%d=\n",err);
if (err)
goto exit2;
err = device_create_file(&client->dev, &sensor_dev_attr_lux.dev_attr);
printk("==device_create_file2==err=%d=\n",err);
if (err)
goto exit_remove1;
/* Register sysfs hooks */
data->hwmon_dev = hwmon_device_register(&client->dev);
dev_info(&client->dev, " build time %s %s\n", __DATE__, __TIME__);
if (IS_ERR(data->hwmon_dev)) {
err = PTR_ERR(data->hwmon_dev);
goto exit_remove2;
}
data->vdd_reg = vdd_reg;
return 0;
exit_remove2:
device_remove_file(&client->dev, &sensor_dev_attr_lux.dev_attr);
exit_remove1:
device_remove_file(&client->dev, &sensor_dev_attr_enable.dev_attr);
exit2:
kfree(data);
exit1:
if (vdd_reg) {
regulator_put(vdd_reg);
vdd_reg = NULL;
}
stk2211_client = NULL;
return err;
}
static int __devinit stk2211_i2c_remove(struct i2c_client *client)
{
struct stk2211_data *data = i2c_get_clientdata(client);
if (data->vdd_reg) {
regulator_put(data->vdd_reg);
data->vdd_reg = NULL;
}
hwmon_device_unregister(data->hwmon_dev);
device_remove_file(&client->dev, &sensor_dev_attr_enable.dev_attr);
device_remove_file(&client->dev, &sensor_dev_attr_lux.dev_attr);
kfree(data);
return 0;
}
static int stk2211_suspend(struct i2c_client *client, pm_message_t message)
{
int cmd;
struct stk2211_data *data = i2c_get_clientdata(client);
if (!data->enable)
goto exit;
cmd = stk2211_read(client, 0x02);
if (cmd < 0)
goto err;
cmd = (cmd | (1 << ADCPD));
if (stk2211_write(client, 0x03, (char)cmd))
goto err;
exit:
return 0;
err:
return -ENODEV;
}
static int stk2211_resume(struct i2c_client *client)
{
int cmd;
struct stk2211_data *data = i2c_get_clientdata(client);
if (!data->enable)
goto exit;
cmd = stk2211_read(client, 0x02);
if (cmd < 0)
goto err;
cmd = (cmd & (~(1 << ADCPD)));
if (stk2211_write(client, 0x03, (char)cmd))
goto err;
exit:
return 0;
err:
return -ENODEV;
}
static const struct i2c_device_id stk2211_id[] = {
{"stk2211", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, stk2211_id);
static struct i2c_driver stk2211_driver = {
.driver = {
.name = "stk2211",
},
.probe = stk2211_i2c_probe,
.remove = stk2211_i2c_remove,
.suspend = stk2211_suspend,
.resume = stk2211_resume,
.id_table = stk2211_id,
};
static int __init stk2211_init(void)
{
u8 err;
printk("\n===joe1==%s===\n",__func__);
err = i2c_add_driver(&stk2211_driver);
if (err != 0)
pr_err("\n%s:driver registration failed, error=%d \n",
__func__, err);
printk("\n===joe2==%s===n",__func__);
return err;
/*
return i2c_add_driver(&stk2211_driver);;
*/
}
static void __exit stk2211_cleanup(void)
{
i2c_del_driver(&stk2211_driver);
}
module_init(stk2211_init);
module_exit(stk2211_cleanup);
MODULE_AUTHOR("JOE <joe@thtfit.com> MTC, Inc.");
MODULE_DESCRIPTION("STK2211 light sensor driver");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");