437 lines
10 KiB
C
437 lines
10 KiB
C
/*
|
|
* w1_ds2408.c - w1 family 29 (DS2408) driver
|
|
*
|
|
* Copyright (c) 2010 Jean-Francois Dagenais <dagenaisj@sonatest.com>
|
|
*
|
|
* This source code is licensed under the GNU General Public License,
|
|
* Version 2. See the file COPYING for more details.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/device.h>
|
|
#include <linux/types.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "../w1.h"
|
|
#include "../w1_int.h"
|
|
#include "../w1_family.h"
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Jean-Francois Dagenais <dagenaisj@sonatest.com>");
|
|
MODULE_DESCRIPTION("w1 family 29 driver for DS2408 8 Pin IO");
|
|
MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2408));
|
|
|
|
|
|
#define W1_F29_RETRIES 3
|
|
|
|
#define W1_F29_REG_LOGIG_STATE 0x88 /* R */
|
|
#define W1_F29_REG_OUTPUT_LATCH_STATE 0x89 /* R */
|
|
#define W1_F29_REG_ACTIVITY_LATCH_STATE 0x8A /* R */
|
|
#define W1_F29_REG_COND_SEARCH_SELECT_MASK 0x8B /* RW */
|
|
#define W1_F29_REG_COND_SEARCH_POL_SELECT 0x8C /* RW */
|
|
#define W1_F29_REG_CONTROL_AND_STATUS 0x8D /* RW */
|
|
|
|
#define W1_F29_FUNC_READ_PIO_REGS 0xF0
|
|
#define W1_F29_FUNC_CHANN_ACCESS_READ 0xF5
|
|
#define W1_F29_FUNC_CHANN_ACCESS_WRITE 0x5A
|
|
/* also used to write the control/status reg (0x8D): */
|
|
#define W1_F29_FUNC_WRITE_COND_SEARCH_REG 0xCC
|
|
#define W1_F29_FUNC_RESET_ACTIVITY_LATCHES 0xC3
|
|
|
|
#define W1_F29_SUCCESS_CONFIRM_BYTE 0xAA
|
|
|
|
static int _read_reg(struct w1_slave *sl, u8 address, unsigned char* buf)
|
|
{
|
|
u8 wrbuf[3];
|
|
dev_dbg(&sl->dev,
|
|
"Reading with slave: %p, reg addr: %0#4x, buff addr: %p",
|
|
sl, (unsigned int)address, buf);
|
|
|
|
if (!buf)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&sl->master->bus_mutex);
|
|
dev_dbg(&sl->dev, "mutex locked");
|
|
|
|
if (w1_reset_select_slave(sl)) {
|
|
mutex_unlock(&sl->master->bus_mutex);
|
|
return -EIO;
|
|
}
|
|
|
|
wrbuf[0] = W1_F29_FUNC_READ_PIO_REGS;
|
|
wrbuf[1] = address;
|
|
wrbuf[2] = 0;
|
|
w1_write_block(sl->master, wrbuf, 3);
|
|
*buf = w1_read_8(sl->master);
|
|
|
|
mutex_unlock(&sl->master->bus_mutex);
|
|
dev_dbg(&sl->dev, "mutex unlocked");
|
|
return 1;
|
|
}
|
|
|
|
static ssize_t w1_f29_read_state(
|
|
struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
char *buf, loff_t off, size_t count)
|
|
{
|
|
dev_dbg(&kobj_to_w1_slave(kobj)->dev,
|
|
"Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p",
|
|
bin_attr->attr.name, kobj, (unsigned int)off, count, buf);
|
|
if (count != 1 || off != 0)
|
|
return -EFAULT;
|
|
return _read_reg(kobj_to_w1_slave(kobj), W1_F29_REG_LOGIG_STATE, buf);
|
|
}
|
|
|
|
static ssize_t w1_f29_read_output(
|
|
struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
char *buf, loff_t off, size_t count)
|
|
{
|
|
dev_dbg(&kobj_to_w1_slave(kobj)->dev,
|
|
"Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p",
|
|
bin_attr->attr.name, kobj, (unsigned int)off, count, buf);
|
|
if (count != 1 || off != 0)
|
|
return -EFAULT;
|
|
return _read_reg(kobj_to_w1_slave(kobj),
|
|
W1_F29_REG_OUTPUT_LATCH_STATE, buf);
|
|
}
|
|
|
|
static ssize_t w1_f29_read_activity(
|
|
struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
char *buf, loff_t off, size_t count)
|
|
{
|
|
dev_dbg(&kobj_to_w1_slave(kobj)->dev,
|
|
"Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p",
|
|
bin_attr->attr.name, kobj, (unsigned int)off, count, buf);
|
|
if (count != 1 || off != 0)
|
|
return -EFAULT;
|
|
return _read_reg(kobj_to_w1_slave(kobj),
|
|
W1_F29_REG_ACTIVITY_LATCH_STATE, buf);
|
|
}
|
|
|
|
static ssize_t w1_f29_read_cond_search_mask(
|
|
struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
char *buf, loff_t off, size_t count)
|
|
{
|
|
dev_dbg(&kobj_to_w1_slave(kobj)->dev,
|
|
"Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p",
|
|
bin_attr->attr.name, kobj, (unsigned int)off, count, buf);
|
|
if (count != 1 || off != 0)
|
|
return -EFAULT;
|
|
return _read_reg(kobj_to_w1_slave(kobj),
|
|
W1_F29_REG_COND_SEARCH_SELECT_MASK, buf);
|
|
}
|
|
|
|
static ssize_t w1_f29_read_cond_search_polarity(
|
|
struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
char *buf, loff_t off, size_t count)
|
|
{
|
|
if (count != 1 || off != 0)
|
|
return -EFAULT;
|
|
return _read_reg(kobj_to_w1_slave(kobj),
|
|
W1_F29_REG_COND_SEARCH_POL_SELECT, buf);
|
|
}
|
|
|
|
static ssize_t w1_f29_read_status_control(
|
|
struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
char *buf, loff_t off, size_t count)
|
|
{
|
|
if (count != 1 || off != 0)
|
|
return -EFAULT;
|
|
return _read_reg(kobj_to_w1_slave(kobj),
|
|
W1_F29_REG_CONTROL_AND_STATUS, buf);
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t w1_f29_write_output(
|
|
struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
char *buf, loff_t off, size_t count)
|
|
{
|
|
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
|
u8 w1_buf[3];
|
|
u8 readBack;
|
|
unsigned int retries = W1_F29_RETRIES;
|
|
|
|
if (count != 1 || off != 0)
|
|
return -EFAULT;
|
|
|
|
dev_dbg(&sl->dev, "locking mutex for write_output");
|
|
mutex_lock(&sl->master->bus_mutex);
|
|
dev_dbg(&sl->dev, "mutex locked");
|
|
|
|
if (w1_reset_select_slave(sl))
|
|
goto error;
|
|
|
|
while (retries--) {
|
|
w1_buf[0] = W1_F29_FUNC_CHANN_ACCESS_WRITE;
|
|
w1_buf[1] = *buf;
|
|
w1_buf[2] = ~(*buf);
|
|
w1_write_block(sl->master, w1_buf, 3);
|
|
|
|
readBack = w1_read_8(sl->master);
|
|
|
|
if (readBack != W1_F29_SUCCESS_CONFIRM_BYTE) {
|
|
if (w1_reset_resume_command(sl->master))
|
|
goto error;
|
|
/* try again, the slave is ready for a command */
|
|
continue;
|
|
}
|
|
|
|
#ifdef CONFIG_W1_SLAVE_DS2408_READBACK
|
|
/* here the master could read another byte which
|
|
would be the PIO reg (the actual pin logic state)
|
|
since in this driver we don't know which pins are
|
|
in and outs, there's no value to read the state and
|
|
compare. with (*buf) so end this command abruptly: */
|
|
if (w1_reset_resume_command(sl->master))
|
|
goto error;
|
|
|
|
/* go read back the output latches */
|
|
/* (the direct effect of the write above) */
|
|
w1_buf[0] = W1_F29_FUNC_READ_PIO_REGS;
|
|
w1_buf[1] = W1_F29_REG_OUTPUT_LATCH_STATE;
|
|
w1_buf[2] = 0;
|
|
w1_write_block(sl->master, w1_buf, 3);
|
|
/* read the result of the READ_PIO_REGS command */
|
|
if (w1_read_8(sl->master) == *buf)
|
|
#endif
|
|
{
|
|
/* success! */
|
|
mutex_unlock(&sl->master->bus_mutex);
|
|
dev_dbg(&sl->dev,
|
|
"mutex unlocked, retries:%d", retries);
|
|
return 1;
|
|
}
|
|
}
|
|
error:
|
|
mutex_unlock(&sl->master->bus_mutex);
|
|
dev_dbg(&sl->dev, "mutex unlocked in error, retries:%d", retries);
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
|
|
/**
|
|
* Writing to the activity file resets the activity latches.
|
|
*/
|
|
static ssize_t w1_f29_write_activity(
|
|
struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
char *buf, loff_t off, size_t count)
|
|
{
|
|
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
|
unsigned int retries = W1_F29_RETRIES;
|
|
|
|
if (count != 1 || off != 0)
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&sl->master->bus_mutex);
|
|
|
|
if (w1_reset_select_slave(sl))
|
|
goto error;
|
|
|
|
while (retries--) {
|
|
w1_write_8(sl->master, W1_F29_FUNC_RESET_ACTIVITY_LATCHES);
|
|
if (w1_read_8(sl->master) == W1_F29_SUCCESS_CONFIRM_BYTE) {
|
|
mutex_unlock(&sl->master->bus_mutex);
|
|
return 1;
|
|
}
|
|
if (w1_reset_resume_command(sl->master))
|
|
goto error;
|
|
}
|
|
|
|
error:
|
|
mutex_unlock(&sl->master->bus_mutex);
|
|
return -EIO;
|
|
}
|
|
|
|
static ssize_t w1_f29_write_status_control(
|
|
struct file *filp,
|
|
struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
char *buf,
|
|
loff_t off,
|
|
size_t count)
|
|
{
|
|
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
|
u8 w1_buf[4];
|
|
unsigned int retries = W1_F29_RETRIES;
|
|
|
|
if (count != 1 || off != 0)
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&sl->master->bus_mutex);
|
|
|
|
if (w1_reset_select_slave(sl))
|
|
goto error;
|
|
|
|
while (retries--) {
|
|
w1_buf[0] = W1_F29_FUNC_WRITE_COND_SEARCH_REG;
|
|
w1_buf[1] = W1_F29_REG_CONTROL_AND_STATUS;
|
|
w1_buf[2] = 0;
|
|
w1_buf[3] = *buf;
|
|
|
|
w1_write_block(sl->master, w1_buf, 4);
|
|
if (w1_reset_resume_command(sl->master))
|
|
goto error;
|
|
|
|
w1_buf[0] = W1_F29_FUNC_READ_PIO_REGS;
|
|
w1_buf[1] = W1_F29_REG_CONTROL_AND_STATUS;
|
|
w1_buf[2] = 0;
|
|
|
|
w1_write_block(sl->master, w1_buf, 3);
|
|
if (w1_read_8(sl->master) == *buf) {
|
|
/* success! */
|
|
mutex_unlock(&sl->master->bus_mutex);
|
|
return 1;
|
|
}
|
|
}
|
|
error:
|
|
mutex_unlock(&sl->master->bus_mutex);
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
/*
|
|
* This is a special sequence we must do to ensure the P0 output is not stuck
|
|
* in test mode. This is described in rev 2 of the ds2408's datasheet
|
|
* (http://datasheets.maximintegrated.com/en/ds/DS2408.pdf) under
|
|
* "APPLICATION INFORMATION/Power-up timing".
|
|
*/
|
|
static int w1_f29_disable_test_mode(struct w1_slave *sl)
|
|
{
|
|
int res;
|
|
u8 magic[10] = {0x96, };
|
|
u64 rn = le64_to_cpu(*((u64*)&sl->reg_num));
|
|
|
|
memcpy(&magic[1], &rn, 8);
|
|
magic[9] = 0x3C;
|
|
|
|
mutex_lock(&sl->master->bus_mutex);
|
|
|
|
res = w1_reset_bus(sl->master);
|
|
if (res)
|
|
goto out;
|
|
w1_write_block(sl->master, magic, ARRAY_SIZE(magic));
|
|
|
|
res = w1_reset_bus(sl->master);
|
|
out:
|
|
mutex_unlock(&sl->master->bus_mutex);
|
|
return res;
|
|
}
|
|
|
|
static struct bin_attribute w1_f29_sysfs_bin_files[] = {
|
|
{
|
|
.attr = {
|
|
.name = "state",
|
|
.mode = S_IRUGO,
|
|
},
|
|
.size = 1,
|
|
.read = w1_f29_read_state,
|
|
},
|
|
{
|
|
.attr = {
|
|
.name = "output",
|
|
.mode = S_IRUGO | S_IWUSR | S_IWGRP,
|
|
},
|
|
.size = 1,
|
|
.read = w1_f29_read_output,
|
|
.write = w1_f29_write_output,
|
|
},
|
|
{
|
|
.attr = {
|
|
.name = "activity",
|
|
.mode = S_IRUGO,
|
|
},
|
|
.size = 1,
|
|
.read = w1_f29_read_activity,
|
|
.write = w1_f29_write_activity,
|
|
},
|
|
{
|
|
.attr = {
|
|
.name = "cond_search_mask",
|
|
.mode = S_IRUGO,
|
|
},
|
|
.size = 1,
|
|
.read = w1_f29_read_cond_search_mask,
|
|
},
|
|
{
|
|
.attr = {
|
|
.name = "cond_search_polarity",
|
|
.mode = S_IRUGO,
|
|
},
|
|
.size = 1,
|
|
.read = w1_f29_read_cond_search_polarity,
|
|
},
|
|
{
|
|
.attr = {
|
|
.name = "status_control",
|
|
.mode = S_IRUGO | S_IWUSR | S_IWGRP,
|
|
},
|
|
.size = 1,
|
|
.read = w1_f29_read_status_control,
|
|
.write = w1_f29_write_status_control,
|
|
}
|
|
};
|
|
|
|
static int w1_f29_add_slave(struct w1_slave *sl)
|
|
{
|
|
int err = 0;
|
|
int i;
|
|
|
|
err = w1_f29_disable_test_mode(sl);
|
|
if (err)
|
|
return err;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(w1_f29_sysfs_bin_files) && !err; ++i)
|
|
err = sysfs_create_bin_file(
|
|
&sl->dev.kobj,
|
|
&(w1_f29_sysfs_bin_files[i]));
|
|
if (err)
|
|
while (--i >= 0)
|
|
sysfs_remove_bin_file(&sl->dev.kobj,
|
|
&(w1_f29_sysfs_bin_files[i]));
|
|
return err;
|
|
}
|
|
|
|
static void w1_f29_remove_slave(struct w1_slave *sl)
|
|
{
|
|
int i;
|
|
for (i = ARRAY_SIZE(w1_f29_sysfs_bin_files) - 1; i >= 0; --i)
|
|
sysfs_remove_bin_file(&sl->dev.kobj,
|
|
&(w1_f29_sysfs_bin_files[i]));
|
|
}
|
|
|
|
static struct w1_family_ops w1_f29_fops = {
|
|
.add_slave = w1_f29_add_slave,
|
|
.remove_slave = w1_f29_remove_slave,
|
|
};
|
|
|
|
static struct w1_family w1_family_29 = {
|
|
.fid = W1_FAMILY_DS2408,
|
|
.fops = &w1_f29_fops,
|
|
};
|
|
|
|
static int __init w1_f29_init(void)
|
|
{
|
|
return w1_register_family(&w1_family_29);
|
|
}
|
|
|
|
static void __exit w1_f29_exit(void)
|
|
{
|
|
w1_unregister_family(&w1_family_29);
|
|
}
|
|
|
|
module_init(w1_f29_init);
|
|
module_exit(w1_f29_exit);
|