Files
lm-sensors/kernel/chips/pcf8591.c
T
2004-04-17 17:20:01 +00:00

448 lines
17 KiB
C

/*
pcf8591.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 2001 Aurelien Jarno <aurelien@aurel32.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x48, 0x4f, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(pcf8591);
/* The PCF8591 control byte */
/* 7 6 5 4 3 2 1 0 */
/* | 0 |AOEF| AIP | 0 |AINC| AICH | */
#define PCF8591_CONTROL_BYTE_AOEF 0x40 /* Analog Output Enable Flag */
/* (analog output active if 1) */
#define PCF8591_CONTROL_BYTE_AIP 0x30 /* Analog Input Programming */
/* 0x00 = four single ended inputs */
/* 0x10 = three differential inputs */
/* 0x20 = single ended and differential mixed */
/* 0x30 = two differential inputs */
#define PCF8591_CONTROL_BYTE_AINC 0x04 /* Autoincrement Flag */
/* (switch on if 1) */
#define PCF8591_CONTROL_BYTE_AICH 0x03 /* Analog Output Enable Flag */
/* 0x00 = channel 0 */
/* 0x01 = channel 1 */
/* 0x02 = channel 2 */
/* 0x03 = channel 3 */
/* Initial values */
#define PCF8591_INIT_CONTROL_BYTE (PCF8591_CONTROL_BYTE_AOEF | PCF8591_CONTROL_BYTE_AINC)
/* DAC out enabled, four single ended inputs, autoincrement */
#define PCF8591_INIT_AOUT 0 /* DAC out = 0 */
/* Conversions. */
#define REG_TO_SIGNED(reg) (reg & 0x80)?(reg - 256):(reg)
/* Convert signed 8 bit value to signed value */
struct pcf8591_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 control_byte;
u8 ch[4];
u8 aout;
};
static int pcf8591_attach_adapter(struct i2c_adapter *adapter);
static int pcf8591_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int pcf8591_detach_client(struct i2c_client *client);
static void pcf8591_update_client(struct i2c_client *client);
static void pcf8591_init_client(struct i2c_client *client);
static void pcf8591_ain_conf(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void pcf8591_ain(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void pcf8591_aout_enable(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void pcf8591_aout(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
/* This is the driver that will be inserted */
static struct i2c_driver pcf8591_driver = {
.owner = THIS_MODULE,
.name = "PCF8591 sensor chip driver",
.id = I2C_DRIVERID_PCF8591,
.flags = I2C_DF_NOTIFY,
.attach_adapter = pcf8591_attach_adapter,
.detach_client = pcf8591_detach_client,
};
static int pcf8591_id = 0;
/* The /proc/sys entries */
/* -- SENSORS SYSCTL START -- */
#define PCF8591_SYSCTL_AIN_CONF 1000 /* Analog input configuration */
#define PCF8591_SYSCTL_CH0 1001 /* Input channel 1 */
#define PCF8591_SYSCTL_CH1 1002 /* Input channel 2 */
#define PCF8591_SYSCTL_CH2 1003 /* Input channel 3 */
#define PCF8591_SYSCTL_CH3 1004 /* Input channel 4 */
#define PCF8591_SYSCTL_AOUT_ENABLE 1005 /* Analog output enable flag */
#define PCF8591_SYSCTL_AOUT 1006 /* Analog output */
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected PCF8591. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table pcf8591_dir_table_template[] = {
{PCF8591_SYSCTL_AIN_CONF, "ain_conf", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &pcf8591_ain_conf},
{PCF8591_SYSCTL_CH0, "ch0", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &pcf8591_ain},
{PCF8591_SYSCTL_CH1, "ch1", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &pcf8591_ain},
{PCF8591_SYSCTL_CH2, "ch2", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &pcf8591_ain},
{PCF8591_SYSCTL_CH3, "ch3", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &pcf8591_ain},
{PCF8591_SYSCTL_AOUT_ENABLE, "aout_enable", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &pcf8591_aout_enable},
{PCF8591_SYSCTL_AOUT, "aout", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &pcf8591_aout},
{0}
};
/* This function is called when:
* pcf8591_driver is inserted (when this module is loaded), for each
available adapter
* when a new adapter is inserted (and pcf8591_driver is still present) */
static int pcf8591_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, pcf8591_detect);
}
/* This function is called by i2c_detect */
int pcf8591_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct pcf8591_data *data;
int err = 0;
const char *type_name = "";
const char *client_name = "";
/* Make sure we aren't probing the ISA bus!! This is just a safety check at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk
(KERN_ERR "pcf8591.o: pcf8591_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE
| I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet. */
if (!(data = kmalloc(sizeof(struct pcf8591_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &pcf8591_driver;
new_client->flags = 0;
/* Now, we would do the remaining detection. But the PCF8591 is plainly
impossible to detect! Stupid chip. */
/* Determine the chip type - only one kind supported! */
if (kind <= 0)
kind = pcf8591;
type_name = "pcf8591";
client_name = "PCF8591 chip";
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
new_client->id = pcf8591_id++;
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client,
type_name,
pcf8591_dir_table_template)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
/* Initialize the PCF8591 chip */
pcf8591_init_client(new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
kfree(data);
ERROR0:
return err;
}
static int pcf8591_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct pcf8591_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
(KERN_ERR "pcf8591.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
/* Called when we have found a new PCF8591. */
static void pcf8591_init_client(struct i2c_client *client)
{
struct pcf8591_data *data = client->data;
data->control_byte = PCF8591_INIT_CONTROL_BYTE;
data->aout = PCF8591_INIT_AOUT;
i2c_smbus_write_byte_data(client, data->control_byte, data->aout);
}
static void pcf8591_update_client(struct i2c_client *client)
{
struct pcf8591_data *data = client->data;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ + HZ / 2) ||
(jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk(KERN_DEBUG "Starting pcf8591 update\n");
#endif
i2c_smbus_write_byte(client, data->control_byte);
i2c_smbus_read_byte(client); /* The first byte transmitted contains the */
/* conversion code of the previous read cycled */
/* FLUSH IT ! */
/* Number of byte to read to signed depend on the analog input mode */
data->ch[0] = i2c_smbus_read_byte(client);
data->ch[1] = i2c_smbus_read_byte(client);
/* In all case, read at least two values */
if ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) != 0x30)
data->ch[2] = i2c_smbus_read_byte(client);
/* Read the third value if not in "two differential inputs" mode */
if ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0x00)
data->ch[3] = i2c_smbus_read_byte(client);
/* Read the fourth value only in "four single ended inputs" mode */
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
/* The next few functions are the call-back functions of the /proc/sys and
sysctl files. Which function is used is defined in the ctl_table in
the extra1 field. */
void pcf8591_ain_conf(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct pcf8591_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
results[0] = (data->control_byte & PCF8591_CONTROL_BYTE_AIP) >> 4;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
if (results[0] >= 0 && results[0] <= 3)
{
data->control_byte &= ~PCF8591_CONTROL_BYTE_AIP;
data->control_byte |= (results[0] << 4);
i2c_smbus_write_byte(client, data->control_byte);
data->valid = 0;
}
}
}
}
void pcf8591_ain(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct pcf8591_data *data = client->data;
int nr = ctl_name - PCF8591_SYSCTL_CH0;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
pcf8591_update_client(client);
/* Number of data to show and conversion to signed depend on */
/* the analog input mode */
switch(nr) {
case 0:
if (((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0)
| ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 2))
results[0] = data->ch[0]; /* single ended */
else
results[0] = REG_TO_SIGNED(data->ch[0]);/* differential */
break;
case 1:
if (((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0)
| ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 2))
results[0] = data->ch[1]; /* single ended */
else
results[0] = REG_TO_SIGNED(data->ch[1]);/* differential */
break;
case 2:
if ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 3)
results[0] = 0; /* channel not used */
else if ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0)
results[0] = data->ch[2]; /* single ended */
else
results[0] = REG_TO_SIGNED(data->ch[2]);/* differential */
break;
case 3:
if (((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 0)
| ((data->control_byte & PCF8591_CONTROL_BYTE_AIP) == 2))
results[0] = data->ch[3]; /* single ended */
else
results[0] = 0; /* channel not used */
break;
}
*nrels_mag = 1;
}
}
void pcf8591_aout_enable(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct pcf8591_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
results[0] = !(!(data->control_byte & PCF8591_CONTROL_BYTE_AOEF));
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
if (results[0])
data->control_byte |= PCF8591_CONTROL_BYTE_AOEF;
else
data->control_byte &= ~PCF8591_CONTROL_BYTE_AOEF;
i2c_smbus_write_byte(client, data->control_byte);
}
}
}
void pcf8591_aout(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct pcf8591_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
results[0] = data->aout;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
if (results[0] >= 0 && results[0] <= 255) /* ignore values outside DAC range */
{
data->aout = results[0];
i2c_smbus_write_byte_data(client, data->control_byte, data->aout);
}
}
}
}
static int __init sm_pcf8591_init(void)
{
printk(KERN_INFO "pcf8591.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&pcf8591_driver);
}
static void __exit sm_pcf8591_exit(void)
{
i2c_del_driver(&pcf8591_driver);
}
MODULE_AUTHOR("Aurelien Jarno <aurelien@aurel32.net>");
MODULE_DESCRIPTION("PCF8591 driver");
MODULE_LICENSE("GPL");
module_init(sm_pcf8591_init);
module_exit(sm_pcf8591_exit);