517 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * 1-Wire implementation for the ds2438 chip4 *5 * Copyright (c) 2017 Mariusz Bialonczyk <manio@skyboo.net>6 */7 8#include <linux/kernel.h>9#include <linux/module.h>10#include <linux/device.h>11#include <linux/types.h>12#include <linux/delay.h>13 14#include <linux/w1.h>15 16#define W1_FAMILY_DS2438 0x2617 18#define W1_DS2438_RETRIES 319 20/* Memory commands */21#define W1_DS2438_READ_SCRATCH 0xBE22#define W1_DS2438_WRITE_SCRATCH 0x4E23#define W1_DS2438_COPY_SCRATCH 0x4824#define W1_DS2438_RECALL_MEMORY 0xB825/* Register commands */26#define W1_DS2438_CONVERT_TEMP 0x4427#define W1_DS2438_CONVERT_VOLTAGE 0xB428 29#define DS2438_PAGE_SIZE 830#define DS2438_ADC_INPUT_VAD 031#define DS2438_ADC_INPUT_VDD 132#define DS2438_MAX_CONVERSION_TIME 10 /* ms */33 34/* Page #0 definitions */35#define DS2438_STATUS_REG 0x00 /* Status/Configuration Register */36#define DS2438_STATUS_IAD (1 << 0) /* Current A/D Control Bit */37#define DS2438_STATUS_CA (1 << 1) /* Current Accumulator Configuration */38#define DS2438_STATUS_EE (1 << 2) /* Current Accumulator Shadow Selector bit */39#define DS2438_STATUS_AD (1 << 3) /* Voltage A/D Input Select Bit */40#define DS2438_STATUS_TB (1 << 4) /* Temperature Busy Flag */41#define DS2438_STATUS_NVB (1 << 5) /* Nonvolatile Memory Busy Flag */42#define DS2438_STATUS_ADB (1 << 6) /* A/D Converter Busy Flag */43 44#define DS2438_TEMP_LSB 0x0145#define DS2438_TEMP_MSB 0x0246#define DS2438_VOLTAGE_LSB 0x0347#define DS2438_VOLTAGE_MSB 0x0448#define DS2438_CURRENT_LSB 0x0549#define DS2438_CURRENT_MSB 0x0650#define DS2438_THRESHOLD 0x0751 52/* Page #1 definitions */53#define DS2438_ETM_0 0x0054#define DS2438_ETM_1 0x0155#define DS2438_ETM_2 0x0256#define DS2438_ETM_3 0x0357#define DS2438_ICA 0x0458#define DS2438_OFFSET_LSB 0x0559#define DS2438_OFFSET_MSB 0x0660 61static int w1_ds2438_get_page(struct w1_slave *sl, int pageno, u8 *buf)62{63 unsigned int retries = W1_DS2438_RETRIES;64 u8 w1_buf[2];65 u8 crc;66 size_t count;67 68 while (retries--) {69 if (w1_reset_select_slave(sl))70 continue;71 w1_buf[0] = W1_DS2438_RECALL_MEMORY;72 w1_buf[1] = (u8)pageno;73 w1_write_block(sl->master, w1_buf, 2);74 75 if (w1_reset_select_slave(sl))76 continue;77 w1_buf[0] = W1_DS2438_READ_SCRATCH;78 w1_buf[1] = (u8)pageno;79 w1_write_block(sl->master, w1_buf, 2);80 81 count = w1_read_block(sl->master, buf, DS2438_PAGE_SIZE + 1);82 if (count == DS2438_PAGE_SIZE + 1) {83 crc = w1_calc_crc8(buf, DS2438_PAGE_SIZE);84 85 /* check for correct CRC */86 if ((u8)buf[DS2438_PAGE_SIZE] == crc)87 return 0;88 }89 }90 return -1;91}92 93static int w1_ds2438_get_temperature(struct w1_slave *sl, int16_t *temperature)94{95 unsigned int retries = W1_DS2438_RETRIES;96 u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/];97 unsigned int tm = DS2438_MAX_CONVERSION_TIME;98 unsigned long sleep_rem;99 int ret;100 101 mutex_lock(&sl->master->bus_mutex);102 103 while (retries--) {104 if (w1_reset_select_slave(sl))105 continue;106 w1_write_8(sl->master, W1_DS2438_CONVERT_TEMP);107 108 mutex_unlock(&sl->master->bus_mutex);109 sleep_rem = msleep_interruptible(tm);110 if (sleep_rem != 0) {111 ret = -1;112 goto post_unlock;113 }114 115 if (mutex_lock_interruptible(&sl->master->bus_mutex) != 0) {116 ret = -1;117 goto post_unlock;118 }119 120 break;121 }122 123 if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) {124 *temperature = (((int16_t) w1_buf[DS2438_TEMP_MSB]) << 8) | ((uint16_t) w1_buf[DS2438_TEMP_LSB]);125 ret = 0;126 } else127 ret = -1;128 129 mutex_unlock(&sl->master->bus_mutex);130 131post_unlock:132 return ret;133}134 135static int w1_ds2438_change_config_bit(struct w1_slave *sl, u8 mask, u8 value)136{137 unsigned int retries = W1_DS2438_RETRIES;138 u8 w1_buf[3];139 u8 status;140 int perform_write = 0;141 142 while (retries--) {143 if (w1_reset_select_slave(sl))144 continue;145 w1_buf[0] = W1_DS2438_RECALL_MEMORY;146 w1_buf[1] = 0x00;147 w1_write_block(sl->master, w1_buf, 2);148 149 if (w1_reset_select_slave(sl))150 continue;151 w1_buf[0] = W1_DS2438_READ_SCRATCH;152 w1_buf[1] = 0x00;153 w1_write_block(sl->master, w1_buf, 2);154 155 /* reading one byte of result */156 status = w1_read_8(sl->master);157 158 /* if bit0=1, set a value to a mask for easy compare */159 if (value)160 value = mask;161 162 if ((status & mask) == value)163 return 0; /* already set as requested */164 165 /* changing bit */166 status ^= mask;167 perform_write = 1;168 169 break;170 }171 172 if (perform_write) {173 retries = W1_DS2438_RETRIES;174 while (retries--) {175 if (w1_reset_select_slave(sl))176 continue;177 w1_buf[0] = W1_DS2438_WRITE_SCRATCH;178 w1_buf[1] = 0x00;179 w1_buf[2] = status;180 w1_write_block(sl->master, w1_buf, 3);181 182 if (w1_reset_select_slave(sl))183 continue;184 w1_buf[0] = W1_DS2438_COPY_SCRATCH;185 w1_buf[1] = 0x00;186 w1_write_block(sl->master, w1_buf, 2);187 188 return 0;189 }190 }191 return -1;192}193 194static int w1_ds2438_change_offset_register(struct w1_slave *sl, u8 *value)195{196 unsigned int retries = W1_DS2438_RETRIES;197 u8 w1_buf[9];198 u8 w1_page1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/];199 200 if (w1_ds2438_get_page(sl, 1, w1_page1_buf) == 0) {201 memcpy(&w1_buf[2], w1_page1_buf, DS2438_PAGE_SIZE - 1); /* last register reserved */202 w1_buf[7] = value[0]; /* change only offset register */203 w1_buf[8] = value[1];204 while (retries--) {205 if (w1_reset_select_slave(sl))206 continue;207 w1_buf[0] = W1_DS2438_WRITE_SCRATCH;208 w1_buf[1] = 0x01; /* write to page 1 */209 w1_write_block(sl->master, w1_buf, 9);210 211 if (w1_reset_select_slave(sl))212 continue;213 w1_buf[0] = W1_DS2438_COPY_SCRATCH;214 w1_buf[1] = 0x01;215 w1_write_block(sl->master, w1_buf, 2);216 return 0;217 }218 }219 return -1;220}221 222static int w1_ds2438_get_voltage(struct w1_slave *sl,223 int adc_input, uint16_t *voltage)224{225 unsigned int retries = W1_DS2438_RETRIES;226 u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/];227 unsigned int tm = DS2438_MAX_CONVERSION_TIME;228 unsigned long sleep_rem;229 int ret;230 231 mutex_lock(&sl->master->bus_mutex);232 233 if (w1_ds2438_change_config_bit(sl, DS2438_STATUS_AD, adc_input)) {234 ret = -1;235 goto pre_unlock;236 }237 238 while (retries--) {239 if (w1_reset_select_slave(sl))240 continue;241 w1_write_8(sl->master, W1_DS2438_CONVERT_VOLTAGE);242 243 mutex_unlock(&sl->master->bus_mutex);244 sleep_rem = msleep_interruptible(tm);245 if (sleep_rem != 0) {246 ret = -1;247 goto post_unlock;248 }249 250 if (mutex_lock_interruptible(&sl->master->bus_mutex) != 0) {251 ret = -1;252 goto post_unlock;253 }254 255 break;256 }257 258 if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) {259 *voltage = (((uint16_t) w1_buf[DS2438_VOLTAGE_MSB]) << 8) | ((uint16_t) w1_buf[DS2438_VOLTAGE_LSB]);260 ret = 0;261 } else262 ret = -1;263 264pre_unlock:265 mutex_unlock(&sl->master->bus_mutex);266 267post_unlock:268 return ret;269}270 271static int w1_ds2438_get_current(struct w1_slave *sl, int16_t *voltage)272{273 u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/];274 int ret;275 276 mutex_lock(&sl->master->bus_mutex);277 278 if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) {279 /* The voltage measured across current sense resistor RSENS. */280 *voltage = (((int16_t) w1_buf[DS2438_CURRENT_MSB]) << 8) | ((int16_t) w1_buf[DS2438_CURRENT_LSB]);281 ret = 0;282 } else283 ret = -1;284 285 mutex_unlock(&sl->master->bus_mutex);286 287 return ret;288}289 290static ssize_t iad_write(struct file *filp, struct kobject *kobj,291 struct bin_attribute *bin_attr, char *buf,292 loff_t off, size_t count)293{294 struct w1_slave *sl = kobj_to_w1_slave(kobj);295 int ret;296 297 if (count != 1 || off != 0)298 return -EFAULT;299 300 mutex_lock(&sl->master->bus_mutex);301 302 if (w1_ds2438_change_config_bit(sl, DS2438_STATUS_IAD, *buf & 0x01) == 0)303 ret = 1;304 else305 ret = -EIO;306 307 mutex_unlock(&sl->master->bus_mutex);308 309 return ret;310}311 312static ssize_t iad_read(struct file *filp, struct kobject *kobj,313 struct bin_attribute *bin_attr, char *buf,314 loff_t off, size_t count)315{316 struct w1_slave *sl = kobj_to_w1_slave(kobj);317 int ret;318 int16_t voltage;319 320 if (off != 0)321 return 0;322 if (!buf)323 return -EINVAL;324 325 if (w1_ds2438_get_current(sl, &voltage) == 0)326 ret = snprintf(buf, count, "%i\n", voltage);327 else328 ret = -EIO;329 330 return ret;331}332 333static ssize_t page0_read(struct file *filp, struct kobject *kobj,334 struct bin_attribute *bin_attr, char *buf,335 loff_t off, size_t count)336{337 struct w1_slave *sl = kobj_to_w1_slave(kobj);338 int ret;339 u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/];340 341 if (off != 0)342 return 0;343 if (!buf)344 return -EINVAL;345 346 mutex_lock(&sl->master->bus_mutex);347 348 /* Read no more than page0 size */349 if (count > DS2438_PAGE_SIZE)350 count = DS2438_PAGE_SIZE;351 352 if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) {353 memcpy(buf, &w1_buf, count);354 ret = count;355 } else356 ret = -EIO;357 358 mutex_unlock(&sl->master->bus_mutex);359 360 return ret;361}362 363static ssize_t page1_read(struct file *filp, struct kobject *kobj,364 struct bin_attribute *bin_attr, char *buf,365 loff_t off, size_t count)366{367 struct w1_slave *sl = kobj_to_w1_slave(kobj);368 int ret;369 u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/];370 371 if (off != 0)372 return 0;373 if (!buf)374 return -EINVAL;375 376 mutex_lock(&sl->master->bus_mutex);377 378 /* Read no more than page1 size */379 if (count > DS2438_PAGE_SIZE)380 count = DS2438_PAGE_SIZE;381 382 if (w1_ds2438_get_page(sl, 1, w1_buf) == 0) {383 memcpy(buf, &w1_buf, count);384 ret = count;385 } else386 ret = -EIO;387 388 mutex_unlock(&sl->master->bus_mutex);389 390 return ret;391}392 393static ssize_t offset_write(struct file *filp, struct kobject *kobj,394 struct bin_attribute *bin_attr, char *buf,395 loff_t off, size_t count)396{397 struct w1_slave *sl = kobj_to_w1_slave(kobj);398 int ret;399 400 mutex_lock(&sl->master->bus_mutex);401 402 if (w1_ds2438_change_offset_register(sl, buf) == 0)403 ret = count;404 else405 ret = -EIO;406 407 mutex_unlock(&sl->master->bus_mutex);408 409 return ret;410}411 412static ssize_t temperature_read(struct file *filp, struct kobject *kobj,413 struct bin_attribute *bin_attr, char *buf,414 loff_t off, size_t count)415{416 struct w1_slave *sl = kobj_to_w1_slave(kobj);417 int ret;418 int16_t temp;419 420 if (off != 0)421 return 0;422 if (!buf)423 return -EINVAL;424 425 if (w1_ds2438_get_temperature(sl, &temp) == 0)426 ret = snprintf(buf, count, "%i\n", temp);427 else428 ret = -EIO;429 430 return ret;431}432 433static ssize_t vad_read(struct file *filp, struct kobject *kobj,434 struct bin_attribute *bin_attr, char *buf,435 loff_t off, size_t count)436{437 struct w1_slave *sl = kobj_to_w1_slave(kobj);438 int ret;439 uint16_t voltage;440 441 if (off != 0)442 return 0;443 if (!buf)444 return -EINVAL;445 446 if (w1_ds2438_get_voltage(sl, DS2438_ADC_INPUT_VAD, &voltage) == 0)447 ret = snprintf(buf, count, "%u\n", voltage);448 else449 ret = -EIO;450 451 return ret;452}453 454static ssize_t vdd_read(struct file *filp, struct kobject *kobj,455 struct bin_attribute *bin_attr, char *buf,456 loff_t off, size_t count)457{458 struct w1_slave *sl = kobj_to_w1_slave(kobj);459 int ret;460 uint16_t voltage;461 462 if (off != 0)463 return 0;464 if (!buf)465 return -EINVAL;466 467 if (w1_ds2438_get_voltage(sl, DS2438_ADC_INPUT_VDD, &voltage) == 0)468 ret = snprintf(buf, count, "%u\n", voltage);469 else470 ret = -EIO;471 472 return ret;473}474 475static BIN_ATTR_RW(iad, 0);476static BIN_ATTR_RO(page0, DS2438_PAGE_SIZE);477static BIN_ATTR_RO(page1, DS2438_PAGE_SIZE);478static BIN_ATTR_WO(offset, 2);479static BIN_ATTR_RO(temperature, 0/* real length varies */);480static BIN_ATTR_RO(vad, 0/* real length varies */);481static BIN_ATTR_RO(vdd, 0/* real length varies */);482 483static struct bin_attribute *w1_ds2438_bin_attrs[] = {484 &bin_attr_iad,485 &bin_attr_page0,486 &bin_attr_page1,487 &bin_attr_offset,488 &bin_attr_temperature,489 &bin_attr_vad,490 &bin_attr_vdd,491 NULL,492};493 494static const struct attribute_group w1_ds2438_group = {495 .bin_attrs = w1_ds2438_bin_attrs,496};497 498static const struct attribute_group *w1_ds2438_groups[] = {499 &w1_ds2438_group,500 NULL,501};502 503static const struct w1_family_ops w1_ds2438_fops = {504 .groups = w1_ds2438_groups,505};506 507static struct w1_family w1_ds2438_family = {508 .fid = W1_FAMILY_DS2438,509 .fops = &w1_ds2438_fops,510};511module_w1_family(w1_ds2438_family);512 513MODULE_LICENSE("GPL");514MODULE_AUTHOR("Mariusz Bialonczyk <manio@skyboo.net>");515MODULE_DESCRIPTION("1-wire driver for Maxim/Dallas DS2438 Smart Battery Monitor");516MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2438));517