140b0b3f8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2bd529cfbSEvgeniy Polyakov /* 3bd529cfbSEvgeniy Polyakov * w1_ds2433.c - w1 family 23 (DS2433) driver 4bd529cfbSEvgeniy Polyakov * 5bd529cfbSEvgeniy Polyakov * Copyright (c) 2005 Ben Gardner <bgardner@wabtec.com> 6bd529cfbSEvgeniy Polyakov */ 7bd529cfbSEvgeniy Polyakov 8bd529cfbSEvgeniy Polyakov #include <linux/kernel.h> 9bd529cfbSEvgeniy Polyakov #include <linux/module.h> 10bd529cfbSEvgeniy Polyakov #include <linux/moduleparam.h> 11bd529cfbSEvgeniy Polyakov #include <linux/device.h> 12bd529cfbSEvgeniy Polyakov #include <linux/types.h> 13bd529cfbSEvgeniy Polyakov #include <linux/delay.h> 145a0e3ad6STejun Heo #include <linux/slab.h> 15e9d55f9dSEvgeniy Polyakov #ifdef CONFIG_W1_SLAVE_DS2433_CRC 16bd529cfbSEvgeniy Polyakov #include <linux/crc16.h> 17bd529cfbSEvgeniy Polyakov 18bd529cfbSEvgeniy Polyakov #define CRC16_INIT 0 19bd529cfbSEvgeniy Polyakov #define CRC16_VALID 0xb001 20bd529cfbSEvgeniy Polyakov 21bd529cfbSEvgeniy Polyakov #endif 22bd529cfbSEvgeniy Polyakov 23de0d6dbdSAndrew F. Davis #include <linux/w1.h> 24de0d6dbdSAndrew F. Davis 25de0d6dbdSAndrew F. Davis #define W1_EEPROM_DS2433 0x23 26bd529cfbSEvgeniy Polyakov 27bd529cfbSEvgeniy Polyakov #define W1_EEPROM_SIZE 512 28bd529cfbSEvgeniy Polyakov #define W1_PAGE_COUNT 16 29bd529cfbSEvgeniy Polyakov #define W1_PAGE_SIZE 32 30bd529cfbSEvgeniy Polyakov #define W1_PAGE_BITS 5 31bd529cfbSEvgeniy Polyakov #define W1_PAGE_MASK 0x1F 32bd529cfbSEvgeniy Polyakov 33bd529cfbSEvgeniy Polyakov #define W1_F23_TIME 300 34bd529cfbSEvgeniy Polyakov 35bd529cfbSEvgeniy Polyakov #define W1_F23_READ_EEPROM 0xF0 36bd529cfbSEvgeniy Polyakov #define W1_F23_WRITE_SCRATCH 0x0F 37bd529cfbSEvgeniy Polyakov #define W1_F23_READ_SCRATCH 0xAA 38bd529cfbSEvgeniy Polyakov #define W1_F23_COPY_SCRATCH 0x55 39bd529cfbSEvgeniy Polyakov 40bd529cfbSEvgeniy Polyakov struct w1_f23_data { 41bd529cfbSEvgeniy Polyakov u8 memory[W1_EEPROM_SIZE]; 42bd529cfbSEvgeniy Polyakov u32 validcrc; 43bd529cfbSEvgeniy Polyakov }; 44bd529cfbSEvgeniy Polyakov 45*a866ce3bSKrzysztof Kozlowski /* 46bd529cfbSEvgeniy Polyakov * Check the file size bounds and adjusts count as needed. 47bd529cfbSEvgeniy Polyakov * This would not be needed if the file size didn't reset to 0 after a write. 48bd529cfbSEvgeniy Polyakov */ 49bd529cfbSEvgeniy Polyakov static inline size_t w1_f23_fix_count(loff_t off, size_t count, size_t size) 50bd529cfbSEvgeniy Polyakov { 51bd529cfbSEvgeniy Polyakov if (off > size) 52bd529cfbSEvgeniy Polyakov return 0; 53bd529cfbSEvgeniy Polyakov 54bd529cfbSEvgeniy Polyakov if ((off + count) > size) 55bd529cfbSEvgeniy Polyakov return (size - off); 56bd529cfbSEvgeniy Polyakov 57bd529cfbSEvgeniy Polyakov return count; 58bd529cfbSEvgeniy Polyakov } 59bd529cfbSEvgeniy Polyakov 60e9d55f9dSEvgeniy Polyakov #ifdef CONFIG_W1_SLAVE_DS2433_CRC 61bd529cfbSEvgeniy Polyakov static int w1_f23_refresh_block(struct w1_slave *sl, struct w1_f23_data *data, 62bd529cfbSEvgeniy Polyakov int block) 63bd529cfbSEvgeniy Polyakov { 64bd529cfbSEvgeniy Polyakov u8 wrbuf[3]; 65bd529cfbSEvgeniy Polyakov int off = block * W1_PAGE_SIZE; 66bd529cfbSEvgeniy Polyakov 67bd529cfbSEvgeniy Polyakov if (data->validcrc & (1 << block)) 68bd529cfbSEvgeniy Polyakov return 0; 69bd529cfbSEvgeniy Polyakov 70bd529cfbSEvgeniy Polyakov if (w1_reset_select_slave(sl)) { 71bd529cfbSEvgeniy Polyakov data->validcrc = 0; 72bd529cfbSEvgeniy Polyakov return -EIO; 73bd529cfbSEvgeniy Polyakov } 74bd529cfbSEvgeniy Polyakov 75bd529cfbSEvgeniy Polyakov wrbuf[0] = W1_F23_READ_EEPROM; 76bd529cfbSEvgeniy Polyakov wrbuf[1] = off & 0xff; 77bd529cfbSEvgeniy Polyakov wrbuf[2] = off >> 8; 78bd529cfbSEvgeniy Polyakov w1_write_block(sl->master, wrbuf, 3); 79bd529cfbSEvgeniy Polyakov w1_read_block(sl->master, &data->memory[off], W1_PAGE_SIZE); 80bd529cfbSEvgeniy Polyakov 81bd529cfbSEvgeniy Polyakov /* cache the block if the CRC is valid */ 82bd529cfbSEvgeniy Polyakov if (crc16(CRC16_INIT, &data->memory[off], W1_PAGE_SIZE) == CRC16_VALID) 83bd529cfbSEvgeniy Polyakov data->validcrc |= (1 << block); 84bd529cfbSEvgeniy Polyakov 85bd529cfbSEvgeniy Polyakov return 0; 86bd529cfbSEvgeniy Polyakov } 87e9d55f9dSEvgeniy Polyakov #endif /* CONFIG_W1_SLAVE_DS2433_CRC */ 88bd529cfbSEvgeniy Polyakov 8938f40982SGreg Kroah-Hartman static ssize_t eeprom_read(struct file *filp, struct kobject *kobj, 9038f40982SGreg Kroah-Hartman struct bin_attribute *bin_attr, char *buf, 9138f40982SGreg Kroah-Hartman loff_t off, size_t count) 92bd529cfbSEvgeniy Polyakov { 93bd529cfbSEvgeniy Polyakov struct w1_slave *sl = kobj_to_w1_slave(kobj); 94e9d55f9dSEvgeniy Polyakov #ifdef CONFIG_W1_SLAVE_DS2433_CRC 95bd529cfbSEvgeniy Polyakov struct w1_f23_data *data = sl->family_data; 96bd529cfbSEvgeniy Polyakov int i, min_page, max_page; 97bd529cfbSEvgeniy Polyakov #else 98bd529cfbSEvgeniy Polyakov u8 wrbuf[3]; 99bd529cfbSEvgeniy Polyakov #endif 100bd529cfbSEvgeniy Polyakov 101bd529cfbSEvgeniy Polyakov if ((count = w1_f23_fix_count(off, count, W1_EEPROM_SIZE)) == 0) 102bd529cfbSEvgeniy Polyakov return 0; 103bd529cfbSEvgeniy Polyakov 104b02f8bedSNeilBrown mutex_lock(&sl->master->bus_mutex); 105bd529cfbSEvgeniy Polyakov 106e9d55f9dSEvgeniy Polyakov #ifdef CONFIG_W1_SLAVE_DS2433_CRC 107bd529cfbSEvgeniy Polyakov 108bd529cfbSEvgeniy Polyakov min_page = (off >> W1_PAGE_BITS); 109bd529cfbSEvgeniy Polyakov max_page = (off + count - 1) >> W1_PAGE_BITS; 110bd529cfbSEvgeniy Polyakov for (i = min_page; i <= max_page; i++) { 111bd529cfbSEvgeniy Polyakov if (w1_f23_refresh_block(sl, data, i)) { 112bd529cfbSEvgeniy Polyakov count = -EIO; 113bd529cfbSEvgeniy Polyakov goto out_up; 114bd529cfbSEvgeniy Polyakov } 115bd529cfbSEvgeniy Polyakov } 116bd529cfbSEvgeniy Polyakov memcpy(buf, &data->memory[off], count); 117bd529cfbSEvgeniy Polyakov 118e9d55f9dSEvgeniy Polyakov #else /* CONFIG_W1_SLAVE_DS2433_CRC */ 119bd529cfbSEvgeniy Polyakov 120bd529cfbSEvgeniy Polyakov /* read directly from the EEPROM */ 121bd529cfbSEvgeniy Polyakov if (w1_reset_select_slave(sl)) { 122bd529cfbSEvgeniy Polyakov count = -EIO; 123bd529cfbSEvgeniy Polyakov goto out_up; 124bd529cfbSEvgeniy Polyakov } 125bd529cfbSEvgeniy Polyakov 126bd529cfbSEvgeniy Polyakov wrbuf[0] = W1_F23_READ_EEPROM; 127bd529cfbSEvgeniy Polyakov wrbuf[1] = off & 0xff; 128bd529cfbSEvgeniy Polyakov wrbuf[2] = off >> 8; 129bd529cfbSEvgeniy Polyakov w1_write_block(sl->master, wrbuf, 3); 130bd529cfbSEvgeniy Polyakov w1_read_block(sl->master, buf, count); 131bd529cfbSEvgeniy Polyakov 132e9d55f9dSEvgeniy Polyakov #endif /* CONFIG_W1_SLAVE_DS2433_CRC */ 133bd529cfbSEvgeniy Polyakov 134bd529cfbSEvgeniy Polyakov out_up: 135b02f8bedSNeilBrown mutex_unlock(&sl->master->bus_mutex); 136bd529cfbSEvgeniy Polyakov 137bd529cfbSEvgeniy Polyakov return count; 138bd529cfbSEvgeniy Polyakov } 139bd529cfbSEvgeniy Polyakov 140bd529cfbSEvgeniy Polyakov /** 141*a866ce3bSKrzysztof Kozlowski * w1_f23_write() - Writes to the scratchpad and reads it back for verification. 142*a866ce3bSKrzysztof Kozlowski * @sl: The slave structure 143*a866ce3bSKrzysztof Kozlowski * @addr: Address for the write 144*a866ce3bSKrzysztof Kozlowski * @len: length must be <= (W1_PAGE_SIZE - (addr & W1_PAGE_MASK)) 145*a866ce3bSKrzysztof Kozlowski * @data: The data to write 146*a866ce3bSKrzysztof Kozlowski * 147bd529cfbSEvgeniy Polyakov * Then copies the scratchpad to EEPROM. 148bd529cfbSEvgeniy Polyakov * The data must be on one page. 149bd529cfbSEvgeniy Polyakov * The master must be locked. 150bd529cfbSEvgeniy Polyakov * 151*a866ce3bSKrzysztof Kozlowski * Return: 0=Success, -1=failure 152bd529cfbSEvgeniy Polyakov */ 153bd529cfbSEvgeniy Polyakov static int w1_f23_write(struct w1_slave *sl, int addr, int len, const u8 *data) 154bd529cfbSEvgeniy Polyakov { 155dca17146SBen Gardner #ifdef CONFIG_W1_SLAVE_DS2433_CRC 156dca17146SBen Gardner struct w1_f23_data *f23 = sl->family_data; 157dca17146SBen Gardner #endif 158bd529cfbSEvgeniy Polyakov u8 wrbuf[4]; 159bd529cfbSEvgeniy Polyakov u8 rdbuf[W1_PAGE_SIZE + 3]; 160bd529cfbSEvgeniy Polyakov u8 es = (addr + len - 1) & 0x1f; 161bd529cfbSEvgeniy Polyakov 162bd529cfbSEvgeniy Polyakov /* Write the data to the scratchpad */ 163bd529cfbSEvgeniy Polyakov if (w1_reset_select_slave(sl)) 164bd529cfbSEvgeniy Polyakov return -1; 165bd529cfbSEvgeniy Polyakov 166bd529cfbSEvgeniy Polyakov wrbuf[0] = W1_F23_WRITE_SCRATCH; 167bd529cfbSEvgeniy Polyakov wrbuf[1] = addr & 0xff; 168bd529cfbSEvgeniy Polyakov wrbuf[2] = addr >> 8; 169bd529cfbSEvgeniy Polyakov 170bd529cfbSEvgeniy Polyakov w1_write_block(sl->master, wrbuf, 3); 171bd529cfbSEvgeniy Polyakov w1_write_block(sl->master, data, len); 172bd529cfbSEvgeniy Polyakov 173bd529cfbSEvgeniy Polyakov /* Read the scratchpad and verify */ 174bd529cfbSEvgeniy Polyakov if (w1_reset_select_slave(sl)) 175bd529cfbSEvgeniy Polyakov return -1; 176bd529cfbSEvgeniy Polyakov 177bd529cfbSEvgeniy Polyakov w1_write_8(sl->master, W1_F23_READ_SCRATCH); 178bd529cfbSEvgeniy Polyakov w1_read_block(sl->master, rdbuf, len + 3); 179bd529cfbSEvgeniy Polyakov 180bd529cfbSEvgeniy Polyakov /* Compare what was read against the data written */ 181bd529cfbSEvgeniy Polyakov if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) || 182bd529cfbSEvgeniy Polyakov (rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0)) 183bd529cfbSEvgeniy Polyakov return -1; 184bd529cfbSEvgeniy Polyakov 185bd529cfbSEvgeniy Polyakov /* Copy the scratchpad to EEPROM */ 186bd529cfbSEvgeniy Polyakov if (w1_reset_select_slave(sl)) 187bd529cfbSEvgeniy Polyakov return -1; 188bd529cfbSEvgeniy Polyakov 189bd529cfbSEvgeniy Polyakov wrbuf[0] = W1_F23_COPY_SCRATCH; 190bd529cfbSEvgeniy Polyakov wrbuf[3] = es; 191bd529cfbSEvgeniy Polyakov w1_write_block(sl->master, wrbuf, 4); 192bd529cfbSEvgeniy Polyakov 193bd529cfbSEvgeniy Polyakov /* Sleep for 5 ms to wait for the write to complete */ 194bd529cfbSEvgeniy Polyakov msleep(5); 195bd529cfbSEvgeniy Polyakov 196bd529cfbSEvgeniy Polyakov /* Reset the bus to wake up the EEPROM (this may not be needed) */ 197bd529cfbSEvgeniy Polyakov w1_reset_bus(sl->master); 198dca17146SBen Gardner #ifdef CONFIG_W1_SLAVE_DS2433_CRC 199dca17146SBen Gardner f23->validcrc &= ~(1 << (addr >> W1_PAGE_BITS)); 200dca17146SBen Gardner #endif 201bd529cfbSEvgeniy Polyakov return 0; 202bd529cfbSEvgeniy Polyakov } 203bd529cfbSEvgeniy Polyakov 20438f40982SGreg Kroah-Hartman static ssize_t eeprom_write(struct file *filp, struct kobject *kobj, 20538f40982SGreg Kroah-Hartman struct bin_attribute *bin_attr, char *buf, 20638f40982SGreg Kroah-Hartman loff_t off, size_t count) 207bd529cfbSEvgeniy Polyakov { 208bd529cfbSEvgeniy Polyakov struct w1_slave *sl = kobj_to_w1_slave(kobj); 209bd529cfbSEvgeniy Polyakov int addr, len, idx; 210bd529cfbSEvgeniy Polyakov 211bd529cfbSEvgeniy Polyakov if ((count = w1_f23_fix_count(off, count, W1_EEPROM_SIZE)) == 0) 212bd529cfbSEvgeniy Polyakov return 0; 213bd529cfbSEvgeniy Polyakov 214e9d55f9dSEvgeniy Polyakov #ifdef CONFIG_W1_SLAVE_DS2433_CRC 215bd529cfbSEvgeniy Polyakov /* can only write full blocks in cached mode */ 216bd529cfbSEvgeniy Polyakov if ((off & W1_PAGE_MASK) || (count & W1_PAGE_MASK)) { 217bd529cfbSEvgeniy Polyakov dev_err(&sl->dev, "invalid offset/count off=%d cnt=%zd\n", 218bd529cfbSEvgeniy Polyakov (int)off, count); 219bd529cfbSEvgeniy Polyakov return -EINVAL; 220bd529cfbSEvgeniy Polyakov } 221bd529cfbSEvgeniy Polyakov 222bd529cfbSEvgeniy Polyakov /* make sure the block CRCs are valid */ 223bd529cfbSEvgeniy Polyakov for (idx = 0; idx < count; idx += W1_PAGE_SIZE) { 224bd529cfbSEvgeniy Polyakov if (crc16(CRC16_INIT, &buf[idx], W1_PAGE_SIZE) != CRC16_VALID) { 225bd529cfbSEvgeniy Polyakov dev_err(&sl->dev, "bad CRC at offset %d\n", (int)off); 226bd529cfbSEvgeniy Polyakov return -EINVAL; 227bd529cfbSEvgeniy Polyakov } 228bd529cfbSEvgeniy Polyakov } 229e9d55f9dSEvgeniy Polyakov #endif /* CONFIG_W1_SLAVE_DS2433_CRC */ 230bd529cfbSEvgeniy Polyakov 231b02f8bedSNeilBrown mutex_lock(&sl->master->bus_mutex); 232bd529cfbSEvgeniy Polyakov 233bd529cfbSEvgeniy Polyakov /* Can only write data to one page at a time */ 234bd529cfbSEvgeniy Polyakov idx = 0; 235bd529cfbSEvgeniy Polyakov while (idx < count) { 236bd529cfbSEvgeniy Polyakov addr = off + idx; 237bd529cfbSEvgeniy Polyakov len = W1_PAGE_SIZE - (addr & W1_PAGE_MASK); 238bd529cfbSEvgeniy Polyakov if (len > (count - idx)) 239bd529cfbSEvgeniy Polyakov len = count - idx; 240bd529cfbSEvgeniy Polyakov 241bd529cfbSEvgeniy Polyakov if (w1_f23_write(sl, addr, len, &buf[idx]) < 0) { 242bd529cfbSEvgeniy Polyakov count = -EIO; 243bd529cfbSEvgeniy Polyakov goto out_up; 244bd529cfbSEvgeniy Polyakov } 245bd529cfbSEvgeniy Polyakov idx += len; 246bd529cfbSEvgeniy Polyakov } 247bd529cfbSEvgeniy Polyakov 248bd529cfbSEvgeniy Polyakov out_up: 249b02f8bedSNeilBrown mutex_unlock(&sl->master->bus_mutex); 250bd529cfbSEvgeniy Polyakov 251bd529cfbSEvgeniy Polyakov return count; 252bd529cfbSEvgeniy Polyakov } 253bd529cfbSEvgeniy Polyakov 25438f40982SGreg Kroah-Hartman static BIN_ATTR_RW(eeprom, W1_EEPROM_SIZE); 25538f40982SGreg Kroah-Hartman 25638f40982SGreg Kroah-Hartman static struct bin_attribute *w1_f23_bin_attributes[] = { 25738f40982SGreg Kroah-Hartman &bin_attr_eeprom, 25838f40982SGreg Kroah-Hartman NULL, 25938f40982SGreg Kroah-Hartman }; 26038f40982SGreg Kroah-Hartman 26138f40982SGreg Kroah-Hartman static const struct attribute_group w1_f23_group = { 26238f40982SGreg Kroah-Hartman .bin_attrs = w1_f23_bin_attributes, 26338f40982SGreg Kroah-Hartman }; 26438f40982SGreg Kroah-Hartman 26538f40982SGreg Kroah-Hartman static const struct attribute_group *w1_f23_groups[] = { 26638f40982SGreg Kroah-Hartman &w1_f23_group, 26738f40982SGreg Kroah-Hartman NULL, 268bd529cfbSEvgeniy Polyakov }; 269bd529cfbSEvgeniy Polyakov 270bd529cfbSEvgeniy Polyakov static int w1_f23_add_slave(struct w1_slave *sl) 271bd529cfbSEvgeniy Polyakov { 272e9d55f9dSEvgeniy Polyakov #ifdef CONFIG_W1_SLAVE_DS2433_CRC 273bd529cfbSEvgeniy Polyakov struct w1_f23_data *data; 274bd529cfbSEvgeniy Polyakov 275dd00cc48SYoann Padioleau data = kzalloc(sizeof(struct w1_f23_data), GFP_KERNEL); 276bd529cfbSEvgeniy Polyakov if (!data) 277bd529cfbSEvgeniy Polyakov return -ENOMEM; 278bd529cfbSEvgeniy Polyakov sl->family_data = data; 279bd529cfbSEvgeniy Polyakov 280e9d55f9dSEvgeniy Polyakov #endif /* CONFIG_W1_SLAVE_DS2433_CRC */ 28138f40982SGreg Kroah-Hartman return 0; 282bd529cfbSEvgeniy Polyakov } 283bd529cfbSEvgeniy Polyakov 284bd529cfbSEvgeniy Polyakov static void w1_f23_remove_slave(struct w1_slave *sl) 285bd529cfbSEvgeniy Polyakov { 286e9d55f9dSEvgeniy Polyakov #ifdef CONFIG_W1_SLAVE_DS2433_CRC 287bd529cfbSEvgeniy Polyakov kfree(sl->family_data); 288bd529cfbSEvgeniy Polyakov sl->family_data = NULL; 289e9d55f9dSEvgeniy Polyakov #endif /* CONFIG_W1_SLAVE_DS2433_CRC */ 290bd529cfbSEvgeniy Polyakov } 291bd529cfbSEvgeniy Polyakov 29257de2dfcSRikard Falkeborn static const struct w1_family_ops w1_f23_fops = { 293bd529cfbSEvgeniy Polyakov .add_slave = w1_f23_add_slave, 294bd529cfbSEvgeniy Polyakov .remove_slave = w1_f23_remove_slave, 29538f40982SGreg Kroah-Hartman .groups = w1_f23_groups, 296bd529cfbSEvgeniy Polyakov }; 297bd529cfbSEvgeniy Polyakov 298bd529cfbSEvgeniy Polyakov static struct w1_family w1_family_23 = { 299bd529cfbSEvgeniy Polyakov .fid = W1_EEPROM_DS2433, 300bd529cfbSEvgeniy Polyakov .fops = &w1_f23_fops, 301bd529cfbSEvgeniy Polyakov }; 302939fc832SAndrew F. Davis module_w1_family(w1_family_23); 30350fa2951SAndrew F. Davis 30450fa2951SAndrew F. Davis MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>"); 30550fa2951SAndrew F. Davis MODULE_DESCRIPTION("w1 family 23 driver for DS2433, 4kb EEPROM"); 30650fa2951SAndrew F. Davis MODULE_LICENSE("GPL"); 30750fa2951SAndrew F. Davis MODULE_ALIAS("w1-family-" __stringify(W1_EEPROM_DS2433)); 308