1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 *	w1_ds2423.c
4 *
5 * Copyright (c) 2010 Mika Laitio <lamikr@pilppa.org>
6 *
7 * This driver will read and write the value of 4 counters to w1_slave file in
8 * sys filesystem.
9 * Inspired by the w1_therm and w1_ds2431 drivers.
10 */
11
12#include <linux/kernel.h>
13#include <linux/module.h>
14#include <linux/moduleparam.h>
15#include <linux/device.h>
16#include <linux/types.h>
17#include <linux/delay.h>
18#include <linux/crc16.h>
19
20#include <linux/w1.h>
21
22#define W1_COUNTER_DS2423	0x1D
23
24#define CRC16_VALID	0xb001
25#define CRC16_INIT	0
26
27#define COUNTER_COUNT 4
28#define READ_BYTE_COUNT 42
29
30static ssize_t w1_slave_show(struct device *device,
31			     struct device_attribute *attr, char *out_buf)
32{
33	struct w1_slave *sl = dev_to_w1_slave(device);
34	struct w1_master *dev = sl->master;
35	u8 rbuf[COUNTER_COUNT * READ_BYTE_COUNT];
36	u8 wrbuf[3];
37	int rom_addr;
38	int read_byte_count;
39	int result;
40	ssize_t c;
41	int ii;
42	int p;
43	int crc;
44
45	c		= PAGE_SIZE;
46	rom_addr	= (12 << 5) + 31;
47	wrbuf[0]	= 0xA5;
48	wrbuf[1]	= rom_addr & 0xFF;
49	wrbuf[2]	= rom_addr >> 8;
50	mutex_lock(&dev->bus_mutex);
51	if (!w1_reset_select_slave(sl)) {
52		w1_write_block(dev, wrbuf, 3);
53		read_byte_count = 0;
54		for (p = 0; p < 4; p++) {
55			/*
56			 * 1 byte for first bytes in ram page read
57			 * 4 bytes for counter
58			 * 4 bytes for zero bits
59			 * 2 bytes for crc
60			 * 31 remaining bytes from the ram page
61			 */
62			read_byte_count += w1_read_block(dev,
63				rbuf + (p * READ_BYTE_COUNT), READ_BYTE_COUNT);
64			for (ii = 0; ii < READ_BYTE_COUNT; ++ii)
65				c -= snprintf(out_buf + PAGE_SIZE - c,
66					c, "%02x ",
67					rbuf[(p * READ_BYTE_COUNT) + ii]);
68			if (read_byte_count != (p + 1) * READ_BYTE_COUNT) {
69				dev_warn(device,
70					"w1_counter_read() returned %u bytes "
71					"instead of %d bytes wanted.\n",
72					read_byte_count,
73					READ_BYTE_COUNT);
74				c -= snprintf(out_buf + PAGE_SIZE - c,
75					c, "crc=NO\n");
76			} else {
77				if (p == 0) {
78					crc = crc16(CRC16_INIT, wrbuf, 3);
79					crc = crc16(crc, rbuf, 11);
80				} else {
81					/*
82					 * DS2423 calculates crc from all bytes
83					 * read after the previous crc bytes.
84					 */
85					crc = crc16(CRC16_INIT,
86						(rbuf + 11) +
87						((p - 1) * READ_BYTE_COUNT),
88						READ_BYTE_COUNT);
89				}
90				if (crc == CRC16_VALID) {
91					result = 0;
92					for (ii = 4; ii > 0; ii--) {
93						result <<= 8;
94						result |= rbuf[(p *
95							READ_BYTE_COUNT) + ii];
96					}
97					c -= snprintf(out_buf + PAGE_SIZE - c,
98						c, "crc=YES c=%d\n", result);
99				} else {
100					c -= snprintf(out_buf + PAGE_SIZE - c,
101						c, "crc=NO\n");
102				}
103			}
104		}
105	} else {
106		c -= snprintf(out_buf + PAGE_SIZE - c, c, "Connection error");
107	}
108	mutex_unlock(&dev->bus_mutex);
109	return PAGE_SIZE - c;
110}
111
112static DEVICE_ATTR_RO(w1_slave);
113
114static struct attribute *w1_f1d_attrs[] = {
115	&dev_attr_w1_slave.attr,
116	NULL,
117};
118ATTRIBUTE_GROUPS(w1_f1d);
119
120static const struct w1_family_ops w1_f1d_fops = {
121	.groups		= w1_f1d_groups,
122};
123
124static struct w1_family w1_family_1d = {
125	.fid = W1_COUNTER_DS2423,
126	.fops = &w1_f1d_fops,
127};
128module_w1_family(w1_family_1d);
129
130MODULE_AUTHOR("Mika Laitio <lamikr@pilppa.org>");
131MODULE_DESCRIPTION("w1 family 1d driver for DS2423, 4 counters and 4kb ram");
132MODULE_LICENSE("GPL");
133MODULE_ALIAS("w1-family-" __stringify(W1_COUNTER_DS2423));
134