From 30b9291288688932ff889a6e7350973074137bd0 Mon Sep 17 00:00:00 2001 From: "Mark D. Studebaker" Date: Sun, 29 Jul 2001 00:17:14 +0000 Subject: [PATCH] add 12-bit precision mode to ds1621, patch from Christian Zuckschwerdt: Hi, On Fri, 15 Jun 2001, Mark D. Studebaker wrote: > I looked at the patch and I see there are separate > /proc entries for the 9 and 12 bit outputs. > Why not just make the 12 bit output the only output using "temp"?? > I don't understand the need for all the new /proc entries, > except perhaps for debugging inside #ifdef DEBUG. > Would you or Jan be willing to develop a patch that way? > I'm all for increased accuracy as a standard feature - why not? Ok. I rewrote the patch to support standard (9bit) and enhanced (12bit) accuracy via the "temp" entry. The "raw" interface is also gone. There are some quirks needed for the sensors app. It seems that prog/sensors/chips.c fixes the output to %+3.1f Thats not good for 12bit mode which gives you 2 decimals and therefor wrong scaling. Precision is .062 with 12bit (4 decimal bits) instead of .5 with 9bit (1 decimal bit). That way 2 decimals seem a natural choice. On the other hand I see no obvious reason not to fix decimals to 1... I'm lost there -- suggestions anyone? Another problem is that 12bit is slow and laggy. I don't recommend making it the default mode. cu. : Christian git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@1145 7894878c-1315-0410-8ee3-d5d059ff63e0 --- doc/chips/ds1621 | 223 +++++++++++++++++++++++++++++++++------ kernel/chips/ds1621.c | 135 +++++++++++++++++++++--- kernel/include/sensors.h | 2 + lib/chips.h | 3 + 4 files changed, 317 insertions(+), 46 deletions(-) diff --git a/doc/chips/ds1621 b/doc/chips/ds1621 index bee39097..242289db 100644 --- a/doc/chips/ds1621 +++ b/doc/chips/ds1621 @@ -1,21 +1,22 @@ Kernel driver `ds1621.o' -===================== +======================== -Status: Complete and tested, alarms experimental +Status: Complete and tested; enhanced features experimental Supported chips: * Dallas Semiconductor DS1621 Prefix: `ds1621' Addresses scanned: I2C 0x48 - 0x4f (inclusive) Datasheet: Publicly available at the Dallas Semiconductor website - http://www.dalsemi.com + http://www.dalsemi.com/ * Dallas Semiconductor DS1625 Prefix: `ds1621' Addresses scanned: I2C 0x48 - 0x4f (inclusive) Datasheet: Publicly available at the Dallas Semiconductor website - http://www.dalsemi.com + http://www.dalsemi.com/ Author: Christian W. Zuckschwerdt +valueable contributions by Jan M. Sendler Module Parameters @@ -24,8 +25,8 @@ Module Parameters * force: short array (min = 1, max = 48) List of adapter,address pairs to boldly assume to be present * force_ds1621: short array (min = 1, max = 48) - List of adapter,address pairs which are unquestionably assumed to contain - a `ds1621' chip + List of adapter,address pairs which are unquestionably assumed to + contain a `ds1621' chip * ignore: short array (min = 1, max = 48) List of adapter,address pairs not to scan * ignore_range: short array (min = 1, max = 48) @@ -39,28 +40,178 @@ Module Parameters Description ----------- -The DS1621 implements one temperature sensor. Limits can be set through the -Overtemperature register and Hysteresis register. Each value can be -set and read to half-degree accuracy. -An alarm is issued when the temperature gets higher then the Overtemperature -value; or drops below the Hysteresis value. +The DS1621 is a (one instance) digital thermometer and thermostat. It +has both high and low temperature limits which can be user defined +(i. e. programmed into non-volatile on-chip registers). +Temperature range is -55 degree Celsius to +125 in 0.5 increments. You +may convert this into a Fahrenheit range of -67 to +257 degrees with +0.9 steps. Unfortunally though, you will need a hash table or even a +conversion routine for that. -It stays on until reset by the kernel module. +Temperature readings are internally converted into a 9bit value to be +read straight via the i2c-bus, but higher resolutions may be obtained +with little effort via 'precise_temp' access or using a simple algorithm. -All temperatures are in degrees Celcius, and are guaranteed within a -range of -55 to +125 degrees. +As for the thermostat, behavior can also be programmed using the +polarity toggle. +On the one hand ("heater"), the thermostat output of the chip, Tout, +will trigger when the low limit temperature is met or underrun and +stays high until the high limit is met or exceeded. On the other hand +("cooler"), vice versa. +That way "heater" equals "active low", whereas "conditioner" equals +"active high". Please note that the DS1621 data sheet is somewhat +misleading in this point since setting the polarity bit does not +simply invert Tout. -The DS1621 supplies the temperature data in a format common with chips -like the LM75, LM77 and DS75, DS1775. -While the DS1621 has three additional bits of accuracy -(12 vs. 9 for the LM75), the additional bits are not supported. -The driver also supports the DS1625. It does not distinguish the 1625 from -the 1621. The chip prefix in /proc is always ds1621. -The difference in the chips is that the 1621 supports additional registers -for greater accuracy. The driver does not use these registers. -The DS1625 has been discontinued by Dallas Semi, they recommend the -DS1621 for new designs. +A second thing is that, during extensive testing, Tout showed a +tolerance of up to +/- 0.5 degrees even when compared against precise +temperature readings. +Be sure to have a high vs. low temperature limit gap of al least 1.0 +degree Celsius to avoid Tout "bouncing", though! + +As for alarms, you can read the alarm status of the DS1621 via the +'alarms' proc file interface. The result consits mainly of bit 6 and 5 +of the configuration register of the chip; bit 6 (0x40 or 64) is the +high alarm bit and bit 5 (0x20 or 32) the low one. These bits are set +when the high or low limits are met or exceeded and are reset by the +module as soon as the respective temperature ranges are left. + +The alarm registers are in no way suitable to find out about the +actual status of Tout. They will only tell you about its history, +whether or not any of the limits have ever been met or exceeded since +last power-up or reset. Be aware: When testing, it showed that the +status of Tout can change with neither of the alarms set. + +Temperature conversion of the DS1621 takes up to 1000ms; internal +access to non-volatile registers may last for 10ms or below. + +This driver also detects DS1625. As this chip is discontinued, you +should use a DS1621 instead. Detected 1625s also get the +"ds1621"-prefix, but the accuracy registers are not supported. +The latest modification of this driver was not checked against 1625s, +though, but should work well with them. + +Default values written into the DS1621 upon detection are: +Temperature limits: High 60.0; low 0.0 degree Celsius; +Continuous mode (not enabled), Tout active high, alarms reset. + + +Accessing DS1621 via /proc interface (part I) +--------------------------------------------- + +On detection (i.e. insmod, modprobe et al.), directories are being +created for each detected DS1621: + +/proc/sys/dev/sensors/ds1621-<0>-<1>/ +where <0> is the bus the chip was detected on (e. g. i2c-0) +and <1> the chip address ([48..4f]): ./ds1621-i2c-0-48/ + +Inside these directories, there are five files each: +alarms, continuous, enable, polarity and temp. + +The temp, alarms and enable are LM75-like for compatibility reasons: + +The temp file is rw. Reading gives TEMP_HIGH:TEMP_HYST:TEMP, where +TEMP_HIGH is the upper limit temp, TEMP_HYST is the hysteresis (lower +limit) temp and TEMP is the actual temperature. (For things like +ONE_SHOT, see below.) - Writing takes two results (read: input +values): TEMP_HIGH:TEMP_HYST. This provides an easy way to set limits. + +The alarms file is ro. Basically, it is 0x40 ("64") for the high alarm +bit set plus 0x20 ("32") for the low one. Since alarms are being reset +by the module when the limit conditions are no longer met, you should +first read the alarms before you do anything else (such as reading the +temperature), or the information may be lost. + +The polarity file is rw. Reading gives "0" for active high, which +describes a need for cooling corresponds to Tout status. "1" means +active low, so you can connect a powerful heater directly to Tout +(DON'T TRY!). - Writing accepts "0" and "1" accordingly. Again, note +that the statements on page 5 of the DS1621 data sheet are misleading; +change the orientation of the vertical arrows in the sketch there +instead. + +To understand the use of the other things, it is useful to have a +brief look at the internal DS1621 procedures: + + +High Accuracy Temperature Reading +--------------------------------- + +As said before, the temperature issued via the 9bit i2c-bus data is +somewhat arbitrary. Internally, the temperature conversion is of a +different kind that is explained (not so...) well in the DS1621 data +sheet. To cut the long story short: Inside the DS1621 there are two +oscillators, both of them biassed by a temperature coefficient. + +Higher accuracy of the temperature reading can be achieved using the +internal projection, which means taking account of REG_COUNT and +REG_SLOPE (the driver manages them): + +Taken from Dallas Semiconductors App Note 068: 'Increasing Temperature +Resolution on the DS1620' and App Note 105: 'High Resolution +Temperature Measurement with Dallas Direct-to-Digital Temperature +Sensors' + +- Read the 9bit temperature and strip the LSB (Truncate the .5 degs) +- The resulting value is TEMP_READ. +- Then, read REG_COUNT. +- And then, REG_SLOPE. + + TEMP = TEMP_READ - 0.25 + ((REG_SLOPE - REG_COUNT) / REG_SLOPE) + +Note that this is what the DONE bit in the DS1621 configuration +register is good for: Internally, one temperature conversion takes up +to 1000ms. Before that conversion is complete you will not be able to +read valid things out of REG_COUNT and REG_SLOPE. The DONE bit, as you +may have guessed by now, tells you whether the conversion is complete +("done", in plain english) and thus, whether the values you read are +good or not. + +The DS1621 has two modes of operation: "Continuous" conversion, which +can be understood as the default stand-alone mode where the chip gets +the temperature and controls external devices via its Tout pin or +tells other i2c's about it if they care. The other mode is called +"1SHOT", that means that it only figures out about the temperature +when it is explicitly told to do so; this can be seen as power saving +mode. + +Now if you want to read REG_COUNT and REG_SLOPE, you have to either +stop the continuous conversions until the contents of these registers +are valid, or, in 1SHOT mode, you have to have one conversion made. + + + +Accessing DS1621 via /proc interface (part II and end) +------------------------------------------------------ + +The continuous file is rw. Reading gives "1" when the chip is in +continuous mode and "0" for 1SHOT. - Writing accepts "1" to put it +into continuous mode or "0" for 1SHOT. + +The enable file is rw. Reading gives "1" if in continuous mode the +DS1621 is "running" or if in 1SHOT mode it is "shooting". In fact, the +value returned here is the negated DONE bit, just like a "BUSY" +flag. This means, that the "1" tells you that the values in REG_COUNT +and REG_SLOPE are not valid. "0", on the other hand, means that the +continous conversion had been stopped long enough to provide valid +readings and that one shot has successfully been fi- red, +respectively. - Writing accepts "1" to start continuous conversion or +to pull the trigger in 1SHOT mode, and "0" stops continuous conversion +to allow for valid readings. Writing "0" in 1SHOT doesn't make much +sense since you cannot catch a bullet, can you? No, the thing is that +DS1621 does only do one conversion then anyway, and after that, +there's nothing to stop. + +Last, but best try writing "0" to the "continuous" file and after that +"1" to the "enable" file. This gives you the precise temperature with +two digits after the radix (decimal point). +The precise results are only updated if the DONE bit was set, thus you +have to stop continous conversion early enough prior to reading, +i. e. 1000ms. And, on the other hand, you shouldn't forget to fire off +1SHOT in order to get the actual temperature rather than that of the +day before yesterday. So read enable before any of these. Chip Features @@ -68,13 +219,19 @@ Chip Features Chip `ds1621' LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE - temp NONE NONE R 1 - temp_hyst temp temp RW 1 - temp_over temp temp RW 1 - alarms NONE NONE R 0 + temp NONE NONE R 1 + temp_hyst temp temp RW 1 + temp_over temp temp RW 1 + alarms NONE NONE R 0 + enable NONE NONE RW 0 + continuous NONE NONE RW 0 + polarity NONE NONE RW 0 - LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET - temp SENSORS_DS1621_TEMP temp:3 - temp_hyst SENSORS_DS1621_TEMP_HYST temp:2 - temp_over SENSORS_DS1621_TEMP_OVER temp:1 - alarms SENSORS_DS1621_ALARMS alarms:1 + LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET + temp SENSORS_DS1621_TEMP temp:3 + temp_hyst SENSORS_DS1621_TEMP_HYST temp:2 + temp_over SENSORS_DS1621_TEMP_OVER temp:1 + alarms SENSORS_DS1621_ALARMS alarms:1 + enable SENSORS_DS1621_ENABLE enable:1 + continuous SENSORS_DS1621_CONTINUOUS continuous:1 + polarity SENSORS_DS1621_POLARITY polarity:1 diff --git a/kernel/chips/ds1621.c b/kernel/chips/ds1621.c index fc452802..68e2cbcd 100644 --- a/kernel/chips/ds1621.c +++ b/kernel/chips/ds1621.c @@ -48,13 +48,21 @@ static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; /* Insmod parameters */ SENSORS_INSMOD_1(ds1621); -/* Many DS1621 constants specified below - most aren't used */ +/* Many DS1621 constants specified below */ /* Config register used for detection */ /* 7 6 5 4 3 2 1 0 */ /* |Done|THF |TLF |NVB | 1 | 0 |POL |1SHOT| */ #define DS1621_REG_CONFIG_MASK 0x0C #define DS1621_REG_CONFIG_VAL 0x08 +#define DS1621_REG_CONFIG_POLARITY 0x02 +#define DS1621_REG_CONFIG_1SHOT 0x01 +#define DS1621_REG_CONFIG_DONE 0x80 + +/* Note: the done bit is always unset if continuous conversion is in progress. + We need to stop the continuous conversion or switch to single shot + before this bit becomes available! + */ /* The DS1621 registers */ #define DS1621_REG_TEMP 0xAA /* word, RO */ @@ -76,6 +84,8 @@ SENSORS_INSMOD_1(ds1621); (((val) + 2) / 5) << 7),0,0xffff)) #define ALARMS_FROM_REG(val) ((val) & \ (DS1621_ALARM_TEMP_HIGH | DS1621_ALARM_TEMP_LOW)) +#define ITEMP_FROM_REG(val) ((((val & 0x7fff) >> 8)) | \ + ((val & 0x8000)?-256:0)) /* Initial values */ #define DS1621_INIT_TEMP_OVER 600 @@ -89,8 +99,11 @@ struct ds1621_data { char valid; /* !=0 if following fields are valid */ unsigned long last_updated; /* In jiffies */ - u16 temp, temp_over, temp_hyst; /* Register values */ + u16 temp, temp_over, temp_hyst; /* Register values, word */ u8 conf; /* Register encoding, combined */ + + char enable; /* !=0 if we're expected to restart the conversion */ + u8 temp_int, temp_counter, temp_slope; /* Register values, byte */ }; #ifdef MODULE @@ -123,6 +136,10 @@ static void ds1621_alarms(struct i2c_client *client, int operation, int ctl_name, int *nrels_mag, long *results); static void ds1621_enable(struct i2c_client *client, int operation, int ctl_name, int *nrels_mag, long *results); +static void ds1621_continuous(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void ds1621_polarity(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); static void ds1621_update_client(struct i2c_client *client); @@ -144,12 +161,16 @@ static struct i2c_driver ds1621_driver = { is done through one of the 'extra' fields which are initialized when a new copy is allocated. */ static ctl_table ds1621_dir_table_template[] = { - {DS1621_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real, - &i2c_sysctl_real, NULL, &ds1621_temp}, + {DS1621_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_temp}, {DS1621_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_alarms}, {DS1621_SYSCTL_ENABLE, "enable", NULL, 0, 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_enable}, + {DS1621_SYSCTL_CONTINUOUS, "continuous", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_continuous}, + {DS1621_SYSCTL_POLARITY, "polarity", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_polarity}, {0} }; @@ -368,12 +389,26 @@ void ds1621_update_client(struct i2c_client *client) #endif data->conf = ds1621_read_value(client, DS1621_REG_CONF); - /* we should wait for the DONE bit... */ - data->temp = ds1621_read_value(client, DS1621_REG_TEMP); + + data->temp = ds1621_read_value(client, + DS1621_REG_TEMP); data->temp_over = ds1621_read_value(client, DS1621_REG_TEMP_OVER); - data->temp_hyst = - ds1621_read_value(client, DS1621_REG_TEMP_HYST); + data->temp_hyst = ds1621_read_value(client, + DS1621_REG_TEMP_HYST); + + /* wait for the DONE bit before reading extended values */ + + if (data->conf & DS1621_REG_CONFIG_DONE) { + data->temp_counter = ds1621_read_value(client, + DS1621_REG_TEMP_COUNTER); + data->temp_slope = ds1621_read_value(client, + DS1621_REG_TEMP_SLOPE); + data->temp_int = ITEMP_FROM_REG(data->temp); + /* restart the conversion */ + if (data->enable) + ds1621_read_value(client, DS1621_COM_START); + } /* reset alarms if neccessary */ new_conf = data->conf; @@ -398,12 +433,29 @@ void ds1621_temp(struct i2c_client *client, int operation, int ctl_name, { struct ds1621_data *data = client->data; if (operation == SENSORS_PROC_REAL_INFO) - *nrels_mag = 1; + if (!(data->conf & DS1621_REG_CONFIG_DONE) || + (data->temp_counter > data->temp_slope) || + (data->temp_slope == 0)) { + *nrels_mag = 1; + } else { + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_READ) { ds1621_update_client(client); - results[0] = TEMP_FROM_REG(data->temp_over); - results[1] = TEMP_FROM_REG(data->temp_hyst); - results[2] = TEMP_FROM_REG(data->temp); + /* decide wether to calculate more precise temp */ + if (!(data->conf & DS1621_REG_CONFIG_DONE) || + (data->temp_counter > data->temp_slope) || + (data->temp_slope == 0)) { + results[0] = TEMP_FROM_REG(data->temp_over); + results[1] = TEMP_FROM_REG(data->temp_hyst); + results[2] = TEMP_FROM_REG(data->temp); + } else { + results[0] = TEMP_FROM_REG(data->temp_over)*10; + results[1] = TEMP_FROM_REG(data->temp_hyst)*10; + results[2] = data->temp_int * 100 - 25 + + ((data->temp_slope - data->temp_counter) * + 100 / data->temp_slope); + } *nrels_mag = 3; } else if (operation == SENSORS_PROC_REAL_WRITE) { if (*nrels_mag >= 1) { @@ -439,19 +491,76 @@ void ds1621_enable(struct i2c_client *client, int operation, int ctl_name, /* sometimes needed to (re)start the continous conversion */ /* there is no data to read so this might hang your SMBus! */ + struct ds1621_data *data = client->data; if (operation == SENSORS_PROC_REAL_INFO) *nrels_mag = 0; else if (operation == SENSORS_PROC_REAL_READ) { - *nrels_mag = 0; + ds1621_update_client(client); + results[0] = !(data->conf & DS1621_REG_CONFIG_DONE); + *nrels_mag = 1; } else if (operation == SENSORS_PROC_REAL_WRITE) { if (*nrels_mag >= 1) { if (results[0]) { ds1621_read_value(client, DS1621_COM_START); + data->enable=1; } else { ds1621_read_value(client, DS1621_COM_STOP); + data->enable=0; } } else { ds1621_read_value(client, DS1621_COM_START); + data->enable=1; + } + } +} + +void ds1621_continuous(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct ds1621_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + ds1621_update_client(client); + results[0] = !(data->conf & DS1621_REG_CONFIG_1SHOT); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + ds1621_update_client(client); + if (*nrels_mag >= 1) { + if (results[0]) { + ds1621_write_value(client, DS1621_REG_CONF, + data->conf & ~DS1621_REG_CONFIG_1SHOT); + } else { + ds1621_write_value(client, DS1621_REG_CONF, + data->conf | DS1621_REG_CONFIG_1SHOT); + } + } else { + ds1621_write_value(client, DS1621_REG_CONF, + data->conf & ~DS1621_REG_CONFIG_1SHOT); + } + } +} + +void ds1621_polarity(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct ds1621_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + ds1621_update_client(client); + results[0] = !(!(data->conf & DS1621_REG_CONFIG_POLARITY)); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + ds1621_update_client(client); + if (*nrels_mag >= 1) { + if (results[0]) { + ds1621_write_value(client, DS1621_REG_CONF, + data->conf | DS1621_REG_CONFIG_POLARITY); + } else { + ds1621_write_value(client, DS1621_REG_CONF, + data->conf & ~DS1621_REG_CONFIG_POLARITY); + } } } } diff --git a/kernel/include/sensors.h b/kernel/include/sensors.h index 3b613187..29fa2c4e 100644 --- a/kernel/include/sensors.h +++ b/kernel/include/sensors.h @@ -467,6 +467,8 @@ #define DS1621_ALARM_TEMP_HIGH 0x40 #define DS1621_ALARM_TEMP_LOW 0x20 #define DS1621_SYSCTL_ENABLE 2002 +#define DS1621_SYSCTL_CONTINUOUS 2003 +#define DS1621_SYSCTL_POLARITY 2004 #define IT87_SYSCTL_IN0 1000 /* Volts * 100 */ #define IT87_SYSCTL_IN1 1001 diff --git a/lib/chips.h b/lib/chips.h index f0c389be..9febea0b 100644 --- a/lib/chips.h +++ b/lib/chips.h @@ -924,6 +924,9 @@ #define SENSORS_DS1621_TEMP_HYST 52 /* RW */ #define SENSORS_DS1621_TEMP_OVER 53 /* RW */ #define SENSORS_DS1621_ALARMS 81 /* R */ +#define SENSORS_DS1621_ENABLE 82 /* RW */ +#define SENSORS_DS1621_CONTINUOUS 83 /* RW */ +#define SENSORS_DS1621_POLARITY 84 /* RW */ /* ADM1024 chip */