diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/w1 | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/w1')
37 files changed, 12117 insertions, 0 deletions
diff --git a/drivers/w1/Kconfig b/drivers/w1/Kconfig new file mode 100644 index 000000000..6743bde03 --- /dev/null +++ b/drivers/w1/Kconfig @@ -0,0 +1,31 @@ +menuconfig W1 + tristate "Dallas's 1-wire support" + depends on HAS_IOMEM + ---help--- + Dallas' 1-wire bus is useful to connect slow 1-pin devices + such as iButtons and thermal sensors. + + If you want W1 support, you should say Y here. + + This W1 support can also be built as a module. If so, the module + will be called wire. + +if W1 + +config W1_CON + depends on CONNECTOR + bool "Userspace communication over connector" + default y + ---help--- + This allows to communicate with userspace using connector. For more + information see <file:Documentation/connector/connector.txt>. + There are three types of messages between w1 core and userspace: + 1. Events. They are generated each time new master or slave device found + either due to automatic or requested search. + 2. Userspace commands. Includes read/write and search/alarm search commands. + 3. Replies to userspace commands. + +source drivers/w1/masters/Kconfig +source drivers/w1/slaves/Kconfig + +endif # W1 diff --git a/drivers/w1/Makefile b/drivers/w1/Makefile new file mode 100644 index 000000000..6bb0b5496 --- /dev/null +++ b/drivers/w1/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the Dallas's 1-wire bus. +# + +obj-$(CONFIG_W1) += wire.o +wire-objs := w1.o w1_int.o w1_family.o w1_netlink.o w1_io.o + +obj-y += masters/ slaves/ + diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig new file mode 100644 index 000000000..00827d289 --- /dev/null +++ b/drivers/w1/masters/Kconfig @@ -0,0 +1,68 @@ +# +# 1-wire bus master configuration +# + +menu "1-wire Bus Masters" + +config W1_MASTER_MATROX + tristate "Matrox G400 transport layer for 1-wire" + depends on PCI + help + Say Y here if you want to communicate with your 1-wire devices + using Matrox's G400 GPIO pins. + + This support is also available as a module. If so, the module + will be called matrox_w1. + +config W1_MASTER_DS2490 + tristate "DS2490 USB <-> W1 transport layer for 1-wire" + depends on USB + help + Say Y here if you want to have a driver for DS2490 based USB <-> W1 bridges, + for example DS9490*. + + This support is also available as a module. If so, the module + will be called ds2490. + +config W1_MASTER_DS2482 + tristate "Maxim DS2482 I2C to 1-Wire bridge" + depends on I2C + help + If you say yes here you get support for the Maxim DS2482 + I2C to 1-Wire bridge. + + This driver can also be built as a module. If so, the module + will be called ds2482. + +config W1_MASTER_MXC + tristate "Freescale MXC 1-wire busmaster" + depends on ARCH_MXC || COMPILE_TEST + help + Say Y here to enable MXC 1-wire host + +config W1_MASTER_DS1WM + tristate "Maxim DS1WM 1-wire busmaster" + help + Say Y here to enable the DS1WM 1-wire driver, such as that + in HP iPAQ devices like h5xxx, h2200, and ASIC3-based like + hx4700. + +config W1_MASTER_GPIO + tristate "GPIO 1-wire busmaster" + depends on GPIOLIB || COMPILE_TEST + help + Say Y here if you want to communicate with your 1-wire devices using + GPIO pins. This driver uses the GPIO API to control the wire. + + This support is also available as a module. If so, the module + will be called w1-gpio. + +config HDQ_MASTER_OMAP + tristate "OMAP HDQ driver" + depends on ARCH_OMAP + help + Say Y here if you want support for the 1-wire or HDQ Interface + on an OMAP processor. + +endmenu + diff --git a/drivers/w1/masters/Makefile b/drivers/w1/masters/Makefile new file mode 100644 index 000000000..18954cae4 --- /dev/null +++ b/drivers/w1/masters/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for 1-wire bus master drivers. +# + +obj-$(CONFIG_W1_MASTER_MATROX) += matrox_w1.o +obj-$(CONFIG_W1_MASTER_DS2490) += ds2490.o +obj-$(CONFIG_W1_MASTER_DS2482) += ds2482.o +obj-$(CONFIG_W1_MASTER_MXC) += mxc_w1.o + +obj-$(CONFIG_W1_MASTER_DS1WM) += ds1wm.o +obj-$(CONFIG_W1_MASTER_GPIO) += w1-gpio.o +obj-$(CONFIG_HDQ_MASTER_OMAP) += omap_hdq.o diff --git a/drivers/w1/masters/ds1wm.c b/drivers/w1/masters/ds1wm.c new file mode 100644 index 000000000..f661695fb --- /dev/null +++ b/drivers/w1/masters/ds1wm.c @@ -0,0 +1,675 @@ +/* + * 1-wire busmaster driver for DS1WM and ASICs with embedded DS1WMs + * such as HP iPAQs (including h5xxx, h2200, and devices with ASIC3 + * like hx4700). + * + * Copyright (c) 2004-2005, Szabolcs Gyurko <szabolcs.gyurko@tlt.hu> + * Copyright (c) 2004-2007, Matt Reimer <mreimer@vpop.net> + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/mfd/core.h> +#include <linux/mfd/ds1wm.h> +#include <linux/slab.h> + +#include <asm/io.h> + +#include <linux/w1.h> + + +#define DS1WM_CMD 0x00 /* R/W 4 bits command */ +#define DS1WM_DATA 0x01 /* R/W 8 bits, transmit/receive buffer */ +#define DS1WM_INT 0x02 /* R/W interrupt status */ +#define DS1WM_INT_EN 0x03 /* R/W interrupt enable */ +#define DS1WM_CLKDIV 0x04 /* R/W 5 bits of divisor and pre-scale */ +#define DS1WM_CNTRL 0x05 /* R/W master control register (not used yet) */ + +#define DS1WM_CMD_1W_RESET (1 << 0) /* force reset on 1-wire bus */ +#define DS1WM_CMD_SRA (1 << 1) /* enable Search ROM accelerator mode */ +#define DS1WM_CMD_DQ_OUTPUT (1 << 2) /* write only - forces bus low */ +#define DS1WM_CMD_DQ_INPUT (1 << 3) /* read only - reflects state of bus */ +#define DS1WM_CMD_RST (1 << 5) /* software reset */ +#define DS1WM_CMD_OD (1 << 7) /* overdrive */ + +#define DS1WM_INT_PD (1 << 0) /* presence detect */ +#define DS1WM_INT_PDR (1 << 1) /* presence detect result */ +#define DS1WM_INT_TBE (1 << 2) /* tx buffer empty */ +#define DS1WM_INT_TSRE (1 << 3) /* tx shift register empty */ +#define DS1WM_INT_RBF (1 << 4) /* rx buffer full */ +#define DS1WM_INT_RSRF (1 << 5) /* rx shift register full */ + +#define DS1WM_INTEN_EPD (1 << 0) /* enable presence detect int */ +#define DS1WM_INTEN_IAS (1 << 1) /* INTR active state */ +#define DS1WM_INTEN_ETBE (1 << 2) /* enable tx buffer empty int */ +#define DS1WM_INTEN_ETMT (1 << 3) /* enable tx shift register empty int */ +#define DS1WM_INTEN_ERBF (1 << 4) /* enable rx buffer full int */ +#define DS1WM_INTEN_ERSRF (1 << 5) /* enable rx shift register full int */ +#define DS1WM_INTEN_DQO (1 << 6) /* enable direct bus driving ops */ + +#define DS1WM_INTEN_NOT_IAS (~DS1WM_INTEN_IAS) /* all but INTR active state */ + +#define DS1WM_TIMEOUT (HZ * 5) + +static struct { + unsigned long freq; + unsigned long divisor; +} freq[] = { + { 1000000, 0x80 }, + { 2000000, 0x84 }, + { 3000000, 0x81 }, + { 4000000, 0x88 }, + { 5000000, 0x82 }, + { 6000000, 0x85 }, + { 7000000, 0x83 }, + { 8000000, 0x8c }, + { 10000000, 0x86 }, + { 12000000, 0x89 }, + { 14000000, 0x87 }, + { 16000000, 0x90 }, + { 20000000, 0x8a }, + { 24000000, 0x8d }, + { 28000000, 0x8b }, + { 32000000, 0x94 }, + { 40000000, 0x8e }, + { 48000000, 0x91 }, + { 56000000, 0x8f }, + { 64000000, 0x98 }, + { 80000000, 0x92 }, + { 96000000, 0x95 }, + { 112000000, 0x93 }, + { 128000000, 0x9c }, +/* you can continue this table, consult the OPERATION - CLOCK DIVISOR + section of the ds1wm spec sheet. */ +}; + +struct ds1wm_data { + void __iomem *map; + unsigned int bus_shift; /* # of shifts to calc register offsets */ + bool is_hw_big_endian; + struct platform_device *pdev; + const struct mfd_cell *cell; + int irq; + int slave_present; + void *reset_complete; + void *read_complete; + void *write_complete; + int read_error; + /* last byte received */ + u8 read_byte; + /* byte to write that makes all intr disabled, */ + /* considering active_state (IAS) (optimization) */ + u8 int_en_reg_none; + unsigned int reset_recover_delay; /* see ds1wm.h */ +}; + +static inline void ds1wm_write_register(struct ds1wm_data *ds1wm_data, u32 reg, + u8 val) +{ + if (ds1wm_data->is_hw_big_endian) { + switch (ds1wm_data->bus_shift) { + case 0: + iowrite8(val, ds1wm_data->map + (reg << 0)); + break; + case 1: + iowrite16be((u16)val, ds1wm_data->map + (reg << 1)); + break; + case 2: + iowrite32be((u32)val, ds1wm_data->map + (reg << 2)); + break; + } + } else { + switch (ds1wm_data->bus_shift) { + case 0: + iowrite8(val, ds1wm_data->map + (reg << 0)); + break; + case 1: + iowrite16((u16)val, ds1wm_data->map + (reg << 1)); + break; + case 2: + iowrite32((u32)val, ds1wm_data->map + (reg << 2)); + break; + } + } +} + +static inline u8 ds1wm_read_register(struct ds1wm_data *ds1wm_data, u32 reg) +{ + u32 val = 0; + + if (ds1wm_data->is_hw_big_endian) { + switch (ds1wm_data->bus_shift) { + case 0: + val = ioread8(ds1wm_data->map + (reg << 0)); + break; + case 1: + val = ioread16be(ds1wm_data->map + (reg << 1)); + break; + case 2: + val = ioread32be(ds1wm_data->map + (reg << 2)); + break; + } + } else { + switch (ds1wm_data->bus_shift) { + case 0: + val = ioread8(ds1wm_data->map + (reg << 0)); + break; + case 1: + val = ioread16(ds1wm_data->map + (reg << 1)); + break; + case 2: + val = ioread32(ds1wm_data->map + (reg << 2)); + break; + } + } + dev_dbg(&ds1wm_data->pdev->dev, + "ds1wm_read_register reg: %d, 32 bit val:%x\n", reg, val); + return (u8)val; +} + + +static irqreturn_t ds1wm_isr(int isr, void *data) +{ + struct ds1wm_data *ds1wm_data = data; + u8 intr; + u8 inten = ds1wm_read_register(ds1wm_data, DS1WM_INT_EN); + /* if no bits are set in int enable register (except the IAS) + than go no further, reading the regs below has side effects */ + if (!(inten & DS1WM_INTEN_NOT_IAS)) + return IRQ_NONE; + + ds1wm_write_register(ds1wm_data, + DS1WM_INT_EN, ds1wm_data->int_en_reg_none); + + /* this read action clears the INTR and certain flags in ds1wm */ + intr = ds1wm_read_register(ds1wm_data, DS1WM_INT); + + ds1wm_data->slave_present = (intr & DS1WM_INT_PDR) ? 0 : 1; + + if ((intr & DS1WM_INT_TSRE) && ds1wm_data->write_complete) { + inten &= ~DS1WM_INTEN_ETMT; + complete(ds1wm_data->write_complete); + } + if (intr & DS1WM_INT_RBF) { + /* this read clears the RBF flag */ + ds1wm_data->read_byte = ds1wm_read_register(ds1wm_data, + DS1WM_DATA); + inten &= ~DS1WM_INTEN_ERBF; + if (ds1wm_data->read_complete) + complete(ds1wm_data->read_complete); + } + if ((intr & DS1WM_INT_PD) && ds1wm_data->reset_complete) { + inten &= ~DS1WM_INTEN_EPD; + complete(ds1wm_data->reset_complete); + } + + ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, inten); + return IRQ_HANDLED; +} + +static int ds1wm_reset(struct ds1wm_data *ds1wm_data) +{ + unsigned long timeleft; + DECLARE_COMPLETION_ONSTACK(reset_done); + + ds1wm_data->reset_complete = &reset_done; + + /* enable Presence detect only */ + ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, DS1WM_INTEN_EPD | + ds1wm_data->int_en_reg_none); + + ds1wm_write_register(ds1wm_data, DS1WM_CMD, DS1WM_CMD_1W_RESET); + + timeleft = wait_for_completion_timeout(&reset_done, DS1WM_TIMEOUT); + ds1wm_data->reset_complete = NULL; + if (!timeleft) { + dev_err(&ds1wm_data->pdev->dev, "reset failed, timed out\n"); + return 1; + } + + if (!ds1wm_data->slave_present) { + dev_dbg(&ds1wm_data->pdev->dev, "reset: no devices found\n"); + return 1; + } + + if (ds1wm_data->reset_recover_delay) + msleep(ds1wm_data->reset_recover_delay); + + return 0; +} + +static int ds1wm_write(struct ds1wm_data *ds1wm_data, u8 data) +{ + unsigned long timeleft; + DECLARE_COMPLETION_ONSTACK(write_done); + ds1wm_data->write_complete = &write_done; + + ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, + ds1wm_data->int_en_reg_none | DS1WM_INTEN_ETMT); + + ds1wm_write_register(ds1wm_data, DS1WM_DATA, data); + + timeleft = wait_for_completion_timeout(&write_done, DS1WM_TIMEOUT); + + ds1wm_data->write_complete = NULL; + if (!timeleft) { + dev_err(&ds1wm_data->pdev->dev, "write failed, timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static u8 ds1wm_read(struct ds1wm_data *ds1wm_data, unsigned char write_data) +{ + unsigned long timeleft; + u8 intEnable = DS1WM_INTEN_ERBF | ds1wm_data->int_en_reg_none; + DECLARE_COMPLETION_ONSTACK(read_done); + + ds1wm_read_register(ds1wm_data, DS1WM_DATA); + + ds1wm_data->read_complete = &read_done; + ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, intEnable); + + ds1wm_write_register(ds1wm_data, DS1WM_DATA, write_data); + timeleft = wait_for_completion_timeout(&read_done, DS1WM_TIMEOUT); + + ds1wm_data->read_complete = NULL; + if (!timeleft) { + dev_err(&ds1wm_data->pdev->dev, "read failed, timed out\n"); + ds1wm_data->read_error = -ETIMEDOUT; + return 0xFF; + } + ds1wm_data->read_error = 0; + return ds1wm_data->read_byte; +} + +static int ds1wm_find_divisor(int gclk) +{ + int i; + + for (i = ARRAY_SIZE(freq)-1; i >= 0; --i) + if (gclk >= freq[i].freq) + return freq[i].divisor; + + return 0; +} + +static void ds1wm_up(struct ds1wm_data *ds1wm_data) +{ + int divisor; + struct device *dev = &ds1wm_data->pdev->dev; + struct ds1wm_driver_data *plat = dev_get_platdata(dev); + + if (ds1wm_data->cell->enable) + ds1wm_data->cell->enable(ds1wm_data->pdev); + + divisor = ds1wm_find_divisor(plat->clock_rate); + dev_dbg(dev, "found divisor 0x%x for clock %d\n", + divisor, plat->clock_rate); + if (divisor == 0) { + dev_err(dev, "no suitable divisor for %dHz clock\n", + plat->clock_rate); + return; + } + ds1wm_write_register(ds1wm_data, DS1WM_CLKDIV, divisor); + + /* Let the w1 clock stabilize. */ + msleep(1); + + ds1wm_reset(ds1wm_data); +} + +static void ds1wm_down(struct ds1wm_data *ds1wm_data) +{ + ds1wm_reset(ds1wm_data); + + /* Disable interrupts. */ + ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, + ds1wm_data->int_en_reg_none); + + if (ds1wm_data->cell->disable) + ds1wm_data->cell->disable(ds1wm_data->pdev); +} + +/* --------------------------------------------------------------------- */ +/* w1 methods */ + +static u8 ds1wm_read_byte(void *data) +{ + struct ds1wm_data *ds1wm_data = data; + + return ds1wm_read(ds1wm_data, 0xff); +} + +static void ds1wm_write_byte(void *data, u8 byte) +{ + struct ds1wm_data *ds1wm_data = data; + + ds1wm_write(ds1wm_data, byte); +} + +static u8 ds1wm_reset_bus(void *data) +{ + struct ds1wm_data *ds1wm_data = data; + + ds1wm_reset(ds1wm_data); + + return 0; +} + +static void ds1wm_search(void *data, struct w1_master *master_dev, + u8 search_type, w1_slave_found_callback slave_found) +{ + struct ds1wm_data *ds1wm_data = data; + int i; + int ms_discrep_bit = -1; + u64 r = 0; /* holds the progress of the search */ + u64 r_prime, d; + unsigned slaves_found = 0; + unsigned int pass = 0; + + dev_dbg(&ds1wm_data->pdev->dev, "search begin\n"); + while (true) { + ++pass; + if (pass > 100) { + dev_dbg(&ds1wm_data->pdev->dev, + "too many attempts (100), search aborted\n"); + return; + } + + mutex_lock(&master_dev->bus_mutex); + if (ds1wm_reset(ds1wm_data)) { + mutex_unlock(&master_dev->bus_mutex); + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d reset error (or no slaves)\n", pass); + break; + } + + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d r : %0#18llx writing SEARCH_ROM\n", pass, r); + ds1wm_write(ds1wm_data, search_type); + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d entering ASM\n", pass); + ds1wm_write_register(ds1wm_data, DS1WM_CMD, DS1WM_CMD_SRA); + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d beginning nibble loop\n", pass); + + r_prime = 0; + d = 0; + /* we work one nibble at a time */ + /* each nibble is interleaved to form a byte */ + for (i = 0; i < 16; i++) { + + unsigned char resp, _r, _r_prime, _d; + + _r = (r >> (4*i)) & 0xf; + _r = ((_r & 0x1) << 1) | + ((_r & 0x2) << 2) | + ((_r & 0x4) << 3) | + ((_r & 0x8) << 4); + + /* writes _r, then reads back: */ + resp = ds1wm_read(ds1wm_data, _r); + + if (ds1wm_data->read_error) { + dev_err(&ds1wm_data->pdev->dev, + "pass: %d nibble: %d read error\n", pass, i); + break; + } + + _r_prime = ((resp & 0x02) >> 1) | + ((resp & 0x08) >> 2) | + ((resp & 0x20) >> 3) | + ((resp & 0x80) >> 4); + + _d = ((resp & 0x01) >> 0) | + ((resp & 0x04) >> 1) | + ((resp & 0x10) >> 2) | + ((resp & 0x40) >> 3); + + r_prime |= (unsigned long long) _r_prime << (i * 4); + d |= (unsigned long long) _d << (i * 4); + + } + if (ds1wm_data->read_error) { + mutex_unlock(&master_dev->bus_mutex); + dev_err(&ds1wm_data->pdev->dev, + "pass: %d read error, retrying\n", pass); + break; + } + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d r\': %0#18llx d:%0#18llx\n", + pass, r_prime, d); + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d nibble loop complete, exiting ASM\n", pass); + ds1wm_write_register(ds1wm_data, DS1WM_CMD, ~DS1WM_CMD_SRA); + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d resetting bus\n", pass); + ds1wm_reset(ds1wm_data); + mutex_unlock(&master_dev->bus_mutex); + if ((r_prime & ((u64)1 << 63)) && (d & ((u64)1 << 63))) { + dev_err(&ds1wm_data->pdev->dev, + "pass: %d bus error, retrying\n", pass); + continue; /* start over */ + } + + + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d found %0#18llx\n", pass, r_prime); + slave_found(master_dev, r_prime); + ++slaves_found; + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d complete, preparing next pass\n", pass); + + /* any discrepency found which we already choose the + '1' branch is now is now irrelevant we reveal the + next branch with this: */ + d &= ~r; + /* find last bit set, i.e. the most signif. bit set */ + ms_discrep_bit = fls64(d) - 1; + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d new d:%0#18llx MS discrep bit:%d\n", + pass, d, ms_discrep_bit); + + /* prev_ms_discrep_bit = ms_discrep_bit; + prepare for next ROM search: */ + if (ms_discrep_bit == -1) + break; + + r = (r & ~(~0ull << (ms_discrep_bit))) | 1 << ms_discrep_bit; + } /* end while true */ + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d total: %d search done ms d bit pos: %d\n", pass, + slaves_found, ms_discrep_bit); +} + +/* --------------------------------------------------------------------- */ + +static struct w1_bus_master ds1wm_master = { + .read_byte = ds1wm_read_byte, + .write_byte = ds1wm_write_byte, + .reset_bus = ds1wm_reset_bus, + .search = ds1wm_search, +}; + +static int ds1wm_probe(struct platform_device *pdev) +{ + struct ds1wm_data *ds1wm_data; + struct ds1wm_driver_data *plat; + struct resource *res; + int ret; + u8 inten; + + if (!pdev) + return -ENODEV; + + ds1wm_data = devm_kzalloc(&pdev->dev, sizeof(*ds1wm_data), GFP_KERNEL); + if (!ds1wm_data) + return -ENOMEM; + + platform_set_drvdata(pdev, ds1wm_data); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + ds1wm_data->map = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!ds1wm_data->map) + return -ENOMEM; + + ds1wm_data->pdev = pdev; + ds1wm_data->cell = mfd_get_cell(pdev); + if (!ds1wm_data->cell) + return -ENODEV; + plat = dev_get_platdata(&pdev->dev); + if (!plat) + return -ENODEV; + + /* how many bits to shift register number to get register offset */ + if (plat->bus_shift > 2) { + dev_err(&ds1wm_data->pdev->dev, + "illegal bus shift %d, not written", + ds1wm_data->bus_shift); + return -EINVAL; + } + + ds1wm_data->bus_shift = plat->bus_shift; + /* make sure resource has space for 8 registers */ + if ((8 << ds1wm_data->bus_shift) > resource_size(res)) { + dev_err(&ds1wm_data->pdev->dev, + "memory resource size %d to small, should be %d\n", + (int)resource_size(res), + 8 << ds1wm_data->bus_shift); + return -EINVAL; + } + + ds1wm_data->is_hw_big_endian = plat->is_hw_big_endian; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) + return -ENXIO; + ds1wm_data->irq = res->start; + ds1wm_data->int_en_reg_none = (plat->active_high ? DS1WM_INTEN_IAS : 0); + ds1wm_data->reset_recover_delay = plat->reset_recover_delay; + + /* Mask interrupts, set IAS before claiming interrupt */ + inten = ds1wm_read_register(ds1wm_data, DS1WM_INT_EN); + ds1wm_write_register(ds1wm_data, + DS1WM_INT_EN, ds1wm_data->int_en_reg_none); + + if (res->flags & IORESOURCE_IRQ_HIGHEDGE) + irq_set_irq_type(ds1wm_data->irq, IRQ_TYPE_EDGE_RISING); + if (res->flags & IORESOURCE_IRQ_LOWEDGE) + irq_set_irq_type(ds1wm_data->irq, IRQ_TYPE_EDGE_FALLING); + if (res->flags & IORESOURCE_IRQ_HIGHLEVEL) + irq_set_irq_type(ds1wm_data->irq, IRQ_TYPE_LEVEL_HIGH); + if (res->flags & IORESOURCE_IRQ_LOWLEVEL) + irq_set_irq_type(ds1wm_data->irq, IRQ_TYPE_LEVEL_LOW); + + ret = devm_request_irq(&pdev->dev, ds1wm_data->irq, ds1wm_isr, + IRQF_SHARED, "ds1wm", ds1wm_data); + if (ret) { + dev_err(&ds1wm_data->pdev->dev, + "devm_request_irq %d failed with errno %d\n", + ds1wm_data->irq, + ret); + + return ret; + } + + ds1wm_up(ds1wm_data); + + ds1wm_master.data = (void *)ds1wm_data; + + ret = w1_add_master_device(&ds1wm_master); + if (ret) + goto err; + + dev_dbg(&ds1wm_data->pdev->dev, + "ds1wm: probe successful, IAS: %d, rec.delay: %d, clockrate: %d, bus-shift: %d, is Hw Big Endian: %d\n", + plat->active_high, + plat->reset_recover_delay, + plat->clock_rate, + ds1wm_data->bus_shift, + ds1wm_data->is_hw_big_endian); + return 0; + +err: + ds1wm_down(ds1wm_data); + + return ret; +} + +#ifdef CONFIG_PM +static int ds1wm_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct ds1wm_data *ds1wm_data = platform_get_drvdata(pdev); + + ds1wm_down(ds1wm_data); + + return 0; +} + +static int ds1wm_resume(struct platform_device *pdev) +{ + struct ds1wm_data *ds1wm_data = platform_get_drvdata(pdev); + + ds1wm_up(ds1wm_data); + + return 0; +} +#else +#define ds1wm_suspend NULL +#define ds1wm_resume NULL +#endif + +static int ds1wm_remove(struct platform_device *pdev) +{ + struct ds1wm_data *ds1wm_data = platform_get_drvdata(pdev); + + w1_remove_master_device(&ds1wm_master); + ds1wm_down(ds1wm_data); + + return 0; +} + +static struct platform_driver ds1wm_driver = { + .driver = { + .name = "ds1wm", + }, + .probe = ds1wm_probe, + .remove = ds1wm_remove, + .suspend = ds1wm_suspend, + .resume = ds1wm_resume +}; + +static int __init ds1wm_init(void) +{ + pr_info("DS1WM w1 busmaster driver - (c) 2004 Szabolcs Gyurko\n"); + return platform_driver_register(&ds1wm_driver); +} + +static void __exit ds1wm_exit(void) +{ + platform_driver_unregister(&ds1wm_driver); +} + +module_init(ds1wm_init); +module_exit(ds1wm_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>, " + "Matt Reimer <mreimer@vpop.net>," + "Jean-Francois Dagenais <dagenaisj@sonatest.com>"); +MODULE_DESCRIPTION("DS1WM w1 busmaster driver"); diff --git a/drivers/w1/masters/ds2482.c b/drivers/w1/masters/ds2482.c new file mode 100644 index 000000000..8b5e598ff --- /dev/null +++ b/drivers/w1/masters/ds2482.c @@ -0,0 +1,565 @@ +/** + * ds2482.c - provides i2c to w1-master bridge(s) + * Copyright (C) 2005 Ben Gardner <bgardner@wabtec.com> + * + * The DS2482 is a sensor chip made by Dallas Semiconductor (Maxim). + * It is a I2C to 1-wire bridge. + * There are two variations: -100 and -800, which have 1 or 8 1-wire ports. + * The complete datasheet can be obtained from MAXIM's website at: + * http://www.maxim-ic.com/quick_view2.cfm/qv_pk/4382 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <asm/delay.h> + +#include <linux/w1.h> + +/** + * Allow the active pullup to be disabled, default is enabled. + * + * Note from the DS2482 datasheet: + * The APU bit controls whether an active pullup (controlled slew-rate + * transistor) or a passive pullup (Rwpu resistor) will be used to drive + * a 1-Wire line from low to high. When APU = 0, active pullup is disabled + * (resistor mode). Active Pullup should always be selected unless there is + * only a single slave on the 1-Wire line. + */ +static int ds2482_active_pullup = 1; +module_param_named(active_pullup, ds2482_active_pullup, int, 0644); +MODULE_PARM_DESC(active_pullup, "Active pullup (apply to all buses): " \ + "0-disable, 1-enable (default)"); + +/** + * The DS2482 registers - there are 3 registers that are addressed by a read + * pointer. The read pointer is set by the last command executed. + * + * To read the data, issue a register read for any address + */ +#define DS2482_CMD_RESET 0xF0 /* No param */ +#define DS2482_CMD_SET_READ_PTR 0xE1 /* Param: DS2482_PTR_CODE_xxx */ +#define DS2482_CMD_CHANNEL_SELECT 0xC3 /* Param: Channel byte - DS2482-800 only */ +#define DS2482_CMD_WRITE_CONFIG 0xD2 /* Param: Config byte */ +#define DS2482_CMD_1WIRE_RESET 0xB4 /* Param: None */ +#define DS2482_CMD_1WIRE_SINGLE_BIT 0x87 /* Param: Bit byte (bit7) */ +#define DS2482_CMD_1WIRE_WRITE_BYTE 0xA5 /* Param: Data byte */ +#define DS2482_CMD_1WIRE_READ_BYTE 0x96 /* Param: None */ +/* Note to read the byte, Set the ReadPtr to Data then read (any addr) */ +#define DS2482_CMD_1WIRE_TRIPLET 0x78 /* Param: Dir byte (bit7) */ + +/* Values for DS2482_CMD_SET_READ_PTR */ +#define DS2482_PTR_CODE_STATUS 0xF0 +#define DS2482_PTR_CODE_DATA 0xE1 +#define DS2482_PTR_CODE_CHANNEL 0xD2 /* DS2482-800 only */ +#define DS2482_PTR_CODE_CONFIG 0xC3 + +/** + * Configure Register bit definitions + * The top 4 bits always read 0. + * To write, the top nibble must be the 1's compl. of the low nibble. + */ +#define DS2482_REG_CFG_1WS 0x08 /* 1-wire speed */ +#define DS2482_REG_CFG_SPU 0x04 /* strong pull-up */ +#define DS2482_REG_CFG_PPM 0x02 /* presence pulse masking */ +#define DS2482_REG_CFG_APU 0x01 /* active pull-up */ + +/* extra configurations - e.g. 1WS */ +static int extra_config; + +/** + * Write and verify codes for the CHANNEL_SELECT command (DS2482-800 only). + * To set the channel, write the value at the index of the channel. + * Read and compare against the corresponding value to verify the change. + */ +static const u8 ds2482_chan_wr[8] = + { 0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87 }; +static const u8 ds2482_chan_rd[8] = + { 0xB8, 0xB1, 0xAA, 0xA3, 0x9C, 0x95, 0x8E, 0x87 }; + + +/** + * Status Register bit definitions (read only) + */ +#define DS2482_REG_STS_DIR 0x80 +#define DS2482_REG_STS_TSB 0x40 +#define DS2482_REG_STS_SBR 0x20 +#define DS2482_REG_STS_RST 0x10 +#define DS2482_REG_STS_LL 0x08 +#define DS2482_REG_STS_SD 0x04 +#define DS2482_REG_STS_PPD 0x02 +#define DS2482_REG_STS_1WB 0x01 + +/* + * Client data (each client gets its own) + */ + +struct ds2482_data; + +struct ds2482_w1_chan { + struct ds2482_data *pdev; + u8 channel; + struct w1_bus_master w1_bm; +}; + +struct ds2482_data { + struct i2c_client *client; + struct mutex access_lock; + + /* 1-wire interface(s) */ + int w1_count; /* 1 or 8 */ + struct ds2482_w1_chan w1_ch[8]; + + /* per-device values */ + u8 channel; + u8 read_prt; /* see DS2482_PTR_CODE_xxx */ + u8 reg_config; +}; + + +/** + * Helper to calculate values for configuration register + * @param conf the raw config value + * @return the value w/ complements that can be written to register + */ +static inline u8 ds2482_calculate_config(u8 conf) +{ + if (ds2482_active_pullup) + conf |= DS2482_REG_CFG_APU; + + return conf | ((~conf & 0x0f) << 4); +} + + +/** + * Sets the read pointer. + * @param pdev The ds2482 client pointer + * @param read_ptr see DS2482_PTR_CODE_xxx above + * @return -1 on failure, 0 on success + */ +static inline int ds2482_select_register(struct ds2482_data *pdev, u8 read_ptr) +{ + if (pdev->read_prt != read_ptr) { + if (i2c_smbus_write_byte_data(pdev->client, + DS2482_CMD_SET_READ_PTR, + read_ptr) < 0) + return -1; + + pdev->read_prt = read_ptr; + } + return 0; +} + +/** + * Sends a command without a parameter + * @param pdev The ds2482 client pointer + * @param cmd DS2482_CMD_RESET, + * DS2482_CMD_1WIRE_RESET, + * DS2482_CMD_1WIRE_READ_BYTE + * @return -1 on failure, 0 on success + */ +static inline int ds2482_send_cmd(struct ds2482_data *pdev, u8 cmd) +{ + if (i2c_smbus_write_byte(pdev->client, cmd) < 0) + return -1; + + pdev->read_prt = DS2482_PTR_CODE_STATUS; + return 0; +} + +/** + * Sends a command with a parameter + * @param pdev The ds2482 client pointer + * @param cmd DS2482_CMD_WRITE_CONFIG, + * DS2482_CMD_1WIRE_SINGLE_BIT, + * DS2482_CMD_1WIRE_WRITE_BYTE, + * DS2482_CMD_1WIRE_TRIPLET + * @param byte The data to send + * @return -1 on failure, 0 on success + */ +static inline int ds2482_send_cmd_data(struct ds2482_data *pdev, + u8 cmd, u8 byte) +{ + if (i2c_smbus_write_byte_data(pdev->client, cmd, byte) < 0) + return -1; + + /* all cmds leave in STATUS, except CONFIG */ + pdev->read_prt = (cmd != DS2482_CMD_WRITE_CONFIG) ? + DS2482_PTR_CODE_STATUS : DS2482_PTR_CODE_CONFIG; + return 0; +} + + +/* + * 1-Wire interface code + */ + +#define DS2482_WAIT_IDLE_TIMEOUT 100 + +/** + * Waits until the 1-wire interface is idle (not busy) + * + * @param pdev Pointer to the device structure + * @return the last value read from status or -1 (failure) + */ +static int ds2482_wait_1wire_idle(struct ds2482_data *pdev) +{ + int temp = -1; + int retries = 0; + + if (!ds2482_select_register(pdev, DS2482_PTR_CODE_STATUS)) { + do { + temp = i2c_smbus_read_byte(pdev->client); + } while ((temp >= 0) && (temp & DS2482_REG_STS_1WB) && + (++retries < DS2482_WAIT_IDLE_TIMEOUT)); + } + + if (retries >= DS2482_WAIT_IDLE_TIMEOUT) + pr_err("%s: timeout on channel %d\n", + __func__, pdev->channel); + + return temp; +} + +/** + * Selects a w1 channel. + * The 1-wire interface must be idle before calling this function. + * + * @param pdev The ds2482 client pointer + * @param channel 0-7 + * @return -1 (failure) or 0 (success) + */ +static int ds2482_set_channel(struct ds2482_data *pdev, u8 channel) +{ + if (i2c_smbus_write_byte_data(pdev->client, DS2482_CMD_CHANNEL_SELECT, + ds2482_chan_wr[channel]) < 0) + return -1; + + pdev->read_prt = DS2482_PTR_CODE_CHANNEL; + pdev->channel = -1; + if (i2c_smbus_read_byte(pdev->client) == ds2482_chan_rd[channel]) { + pdev->channel = channel; + return 0; + } + return -1; +} + + +/** + * Performs the touch-bit function, which writes a 0 or 1 and reads the level. + * + * @param data The ds2482 channel pointer + * @param bit The level to write: 0 or non-zero + * @return The level read: 0 or 1 + */ +static u8 ds2482_w1_touch_bit(void *data, u8 bit) +{ + struct ds2482_w1_chan *pchan = data; + struct ds2482_data *pdev = pchan->pdev; + int status = -1; + + mutex_lock(&pdev->access_lock); + + /* Select the channel */ + ds2482_wait_1wire_idle(pdev); + if (pdev->w1_count > 1) + ds2482_set_channel(pdev, pchan->channel); + + /* Send the touch command, wait until 1WB == 0, return the status */ + if (!ds2482_send_cmd_data(pdev, DS2482_CMD_1WIRE_SINGLE_BIT, + bit ? 0xFF : 0)) + status = ds2482_wait_1wire_idle(pdev); + + mutex_unlock(&pdev->access_lock); + + return (status & DS2482_REG_STS_SBR) ? 1 : 0; +} + +/** + * Performs the triplet function, which reads two bits and writes a bit. + * The bit written is determined by the two reads: + * 00 => dbit, 01 => 0, 10 => 1 + * + * @param data The ds2482 channel pointer + * @param dbit The direction to choose if both branches are valid + * @return b0=read1 b1=read2 b3=bit written + */ +static u8 ds2482_w1_triplet(void *data, u8 dbit) +{ + struct ds2482_w1_chan *pchan = data; + struct ds2482_data *pdev = pchan->pdev; + int status = (3 << 5); + + mutex_lock(&pdev->access_lock); + + /* Select the channel */ + ds2482_wait_1wire_idle(pdev); + if (pdev->w1_count > 1) + ds2482_set_channel(pdev, pchan->channel); + + /* Send the triplet command, wait until 1WB == 0, return the status */ + if (!ds2482_send_cmd_data(pdev, DS2482_CMD_1WIRE_TRIPLET, + dbit ? 0xFF : 0)) + status = ds2482_wait_1wire_idle(pdev); + + mutex_unlock(&pdev->access_lock); + + /* Decode the status */ + return (status >> 5); +} + +/** + * Performs the write byte function. + * + * @param data The ds2482 channel pointer + * @param byte The value to write + */ +static void ds2482_w1_write_byte(void *data, u8 byte) +{ + struct ds2482_w1_chan *pchan = data; + struct ds2482_data *pdev = pchan->pdev; + + mutex_lock(&pdev->access_lock); + + /* Select the channel */ + ds2482_wait_1wire_idle(pdev); + if (pdev->w1_count > 1) + ds2482_set_channel(pdev, pchan->channel); + + /* Send the write byte command */ + ds2482_send_cmd_data(pdev, DS2482_CMD_1WIRE_WRITE_BYTE, byte); + + mutex_unlock(&pdev->access_lock); +} + +/** + * Performs the read byte function. + * + * @param data The ds2482 channel pointer + * @return The value read + */ +static u8 ds2482_w1_read_byte(void *data) +{ + struct ds2482_w1_chan *pchan = data; + struct ds2482_data *pdev = pchan->pdev; + int result; + + mutex_lock(&pdev->access_lock); + + /* Select the channel */ + ds2482_wait_1wire_idle(pdev); + if (pdev->w1_count > 1) + ds2482_set_channel(pdev, pchan->channel); + + /* Send the read byte command */ + ds2482_send_cmd(pdev, DS2482_CMD_1WIRE_READ_BYTE); + + /* Wait until 1WB == 0 */ + ds2482_wait_1wire_idle(pdev); + + /* Select the data register */ + ds2482_select_register(pdev, DS2482_PTR_CODE_DATA); + + /* Read the data byte */ + result = i2c_smbus_read_byte(pdev->client); + + mutex_unlock(&pdev->access_lock); + + return result; +} + + +/** + * Sends a reset on the 1-wire interface + * + * @param data The ds2482 channel pointer + * @return 0=Device present, 1=No device present or error + */ +static u8 ds2482_w1_reset_bus(void *data) +{ + struct ds2482_w1_chan *pchan = data; + struct ds2482_data *pdev = pchan->pdev; + int err; + u8 retval = 1; + + mutex_lock(&pdev->access_lock); + + /* Select the channel */ + ds2482_wait_1wire_idle(pdev); + if (pdev->w1_count > 1) + ds2482_set_channel(pdev, pchan->channel); + + /* Send the reset command */ + err = ds2482_send_cmd(pdev, DS2482_CMD_1WIRE_RESET); + if (err >= 0) { + /* Wait until the reset is complete */ + err = ds2482_wait_1wire_idle(pdev); + retval = !(err & DS2482_REG_STS_PPD); + + /* If the chip did reset since detect, re-config it */ + if (err & DS2482_REG_STS_RST) + ds2482_send_cmd_data(pdev, DS2482_CMD_WRITE_CONFIG, + ds2482_calculate_config(extra_config)); + } + + mutex_unlock(&pdev->access_lock); + + return retval; +} + +static u8 ds2482_w1_set_pullup(void *data, int delay) +{ + struct ds2482_w1_chan *pchan = data; + struct ds2482_data *pdev = pchan->pdev; + u8 retval = 1; + + /* if delay is non-zero activate the pullup, + * the strong pullup will be automatically deactivated + * by the master, so do not explicitly deactive it + */ + if (delay) { + /* both waits are crucial, otherwise devices might not be + * powered long enough, causing e.g. a w1_therm sensor to + * provide wrong conversion results + */ + ds2482_wait_1wire_idle(pdev); + /* note: it seems like both SPU and APU have to be set! */ + retval = ds2482_send_cmd_data(pdev, DS2482_CMD_WRITE_CONFIG, + ds2482_calculate_config(extra_config|DS2482_REG_CFG_SPU|DS2482_REG_CFG_APU)); + ds2482_wait_1wire_idle(pdev); + } + + return retval; +} + + +static int ds2482_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ds2482_data *data; + int err = -ENODEV; + int temp1; + int idx; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA | + I2C_FUNC_SMBUS_BYTE)) + return -ENODEV; + + if (!(data = kzalloc(sizeof(struct ds2482_data), GFP_KERNEL))) { + err = -ENOMEM; + goto exit; + } + + data->client = client; + i2c_set_clientdata(client, data); + + /* Reset the device (sets the read_ptr to status) */ + if (ds2482_send_cmd(data, DS2482_CMD_RESET) < 0) { + dev_warn(&client->dev, "DS2482 reset failed.\n"); + goto exit_free; + } + + /* Sleep at least 525ns to allow the reset to complete */ + ndelay(525); + + /* Read the status byte - only reset bit and line should be set */ + temp1 = i2c_smbus_read_byte(client); + if (temp1 != (DS2482_REG_STS_LL | DS2482_REG_STS_RST)) { + dev_warn(&client->dev, "DS2482 reset status " + "0x%02X - not a DS2482\n", temp1); + goto exit_free; + } + + /* Detect the 8-port version */ + data->w1_count = 1; + if (ds2482_set_channel(data, 7) == 0) + data->w1_count = 8; + + /* Set all config items to 0 (off) */ + ds2482_send_cmd_data(data, DS2482_CMD_WRITE_CONFIG, + ds2482_calculate_config(extra_config)); + + mutex_init(&data->access_lock); + + /* Register 1-wire interface(s) */ + for (idx = 0; idx < data->w1_count; idx++) { + data->w1_ch[idx].pdev = data; + data->w1_ch[idx].channel = idx; + + /* Populate all the w1 bus master stuff */ + data->w1_ch[idx].w1_bm.data = &data->w1_ch[idx]; + data->w1_ch[idx].w1_bm.read_byte = ds2482_w1_read_byte; + data->w1_ch[idx].w1_bm.write_byte = ds2482_w1_write_byte; + data->w1_ch[idx].w1_bm.touch_bit = ds2482_w1_touch_bit; + data->w1_ch[idx].w1_bm.triplet = ds2482_w1_triplet; + data->w1_ch[idx].w1_bm.reset_bus = ds2482_w1_reset_bus; + data->w1_ch[idx].w1_bm.set_pullup = ds2482_w1_set_pullup; + + err = w1_add_master_device(&data->w1_ch[idx].w1_bm); + if (err) { + data->w1_ch[idx].pdev = NULL; + goto exit_w1_remove; + } + } + + return 0; + +exit_w1_remove: + for (idx = 0; idx < data->w1_count; idx++) { + if (data->w1_ch[idx].pdev != NULL) + w1_remove_master_device(&data->w1_ch[idx].w1_bm); + } +exit_free: + kfree(data); +exit: + return err; +} + +static int ds2482_remove(struct i2c_client *client) +{ + struct ds2482_data *data = i2c_get_clientdata(client); + int idx; + + /* Unregister the 1-wire bridge(s) */ + for (idx = 0; idx < data->w1_count; idx++) { + if (data->w1_ch[idx].pdev != NULL) + w1_remove_master_device(&data->w1_ch[idx].w1_bm); + } + + /* Free the memory */ + kfree(data); + return 0; +} + +/** + * Driver data (common to all clients) + */ +static const struct i2c_device_id ds2482_id[] = { + { "ds2482", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ds2482_id); + +static struct i2c_driver ds2482_driver = { + .driver = { + .name = "ds2482", + }, + .probe = ds2482_probe, + .remove = ds2482_remove, + .id_table = ds2482_id, +}; +module_i2c_driver(ds2482_driver); + +MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>"); +MODULE_DESCRIPTION("DS2482 driver"); +module_param(extra_config, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(extra_config, "Extra Configuration settings 1=APU,2=PPM,3=SPU,8=1WS"); + +MODULE_LICENSE("GPL"); diff --git a/drivers/w1/masters/ds2490.c b/drivers/w1/masters/ds2490.c new file mode 100644 index 000000000..a9fb77585 --- /dev/null +++ b/drivers/w1/masters/ds2490.c @@ -0,0 +1,1105 @@ +/* + * ds2490.c USB to one wire bridge + * + * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/usb.h> +#include <linux/slab.h> + +#include <linux/w1.h> + +/* USB Standard */ +/* USB Control request vendor type */ +#define VENDOR 0x40 + +/* COMMAND TYPE CODES */ +#define CONTROL_CMD 0x00 +#define COMM_CMD 0x01 +#define MODE_CMD 0x02 + +/* CONTROL COMMAND CODES */ +#define CTL_RESET_DEVICE 0x0000 +#define CTL_START_EXE 0x0001 +#define CTL_RESUME_EXE 0x0002 +#define CTL_HALT_EXE_IDLE 0x0003 +#define CTL_HALT_EXE_DONE 0x0004 +#define CTL_FLUSH_COMM_CMDS 0x0007 +#define CTL_FLUSH_RCV_BUFFER 0x0008 +#define CTL_FLUSH_XMT_BUFFER 0x0009 +#define CTL_GET_COMM_CMDS 0x000A + +/* MODE COMMAND CODES */ +#define MOD_PULSE_EN 0x0000 +#define MOD_SPEED_CHANGE_EN 0x0001 +#define MOD_1WIRE_SPEED 0x0002 +#define MOD_STRONG_PU_DURATION 0x0003 +#define MOD_PULLDOWN_SLEWRATE 0x0004 +#define MOD_PROG_PULSE_DURATION 0x0005 +#define MOD_WRITE1_LOWTIME 0x0006 +#define MOD_DSOW0_TREC 0x0007 + +/* COMMUNICATION COMMAND CODES */ +#define COMM_ERROR_ESCAPE 0x0601 +#define COMM_SET_DURATION 0x0012 +#define COMM_BIT_IO 0x0020 +#define COMM_PULSE 0x0030 +#define COMM_1_WIRE_RESET 0x0042 +#define COMM_BYTE_IO 0x0052 +#define COMM_MATCH_ACCESS 0x0064 +#define COMM_BLOCK_IO 0x0074 +#define COMM_READ_STRAIGHT 0x0080 +#define COMM_DO_RELEASE 0x6092 +#define COMM_SET_PATH 0x00A2 +#define COMM_WRITE_SRAM_PAGE 0x00B2 +#define COMM_WRITE_EPROM 0x00C4 +#define COMM_READ_CRC_PROT_PAGE 0x00D4 +#define COMM_READ_REDIRECT_PAGE_CRC 0x21E4 +#define COMM_SEARCH_ACCESS 0x00F4 + +/* Communication command bits */ +#define COMM_TYPE 0x0008 +#define COMM_SE 0x0008 +#define COMM_D 0x0008 +#define COMM_Z 0x0008 +#define COMM_CH 0x0008 +#define COMM_SM 0x0008 +#define COMM_R 0x0008 +#define COMM_IM 0x0001 + +#define COMM_PS 0x4000 +#define COMM_PST 0x4000 +#define COMM_CIB 0x4000 +#define COMM_RTS 0x4000 +#define COMM_DT 0x2000 +#define COMM_SPU 0x1000 +#define COMM_F 0x0800 +#define COMM_NTF 0x0400 +#define COMM_ICP 0x0200 +#define COMM_RST 0x0100 + +#define PULSE_PROG 0x01 +#define PULSE_SPUE 0x02 + +#define BRANCH_MAIN 0xCC +#define BRANCH_AUX 0x33 + +/* Status flags */ +#define ST_SPUA 0x01 /* Strong Pull-up is active */ +#define ST_PRGA 0x02 /* 12V programming pulse is being generated */ +#define ST_12VP 0x04 /* external 12V programming voltage is present */ +#define ST_PMOD 0x08 /* DS2490 powered from USB and external sources */ +#define ST_HALT 0x10 /* DS2490 is currently halted */ +#define ST_IDLE 0x20 /* DS2490 is currently idle */ +#define ST_EPOF 0x80 +/* Status transfer size, 16 bytes status, 16 byte result flags */ +#define ST_SIZE 0x20 + +/* Result Register flags */ +#define RR_DETECT 0xA5 /* New device detected */ +#define RR_NRS 0x01 /* Reset no presence or ... */ +#define RR_SH 0x02 /* short on reset or set path */ +#define RR_APP 0x04 /* alarming presence on reset */ +#define RR_VPP 0x08 /* 12V expected not seen */ +#define RR_CMP 0x10 /* compare error */ +#define RR_CRC 0x20 /* CRC error detected */ +#define RR_RDP 0x40 /* redirected page */ +#define RR_EOS 0x80 /* end of search error */ + +#define SPEED_NORMAL 0x00 +#define SPEED_FLEXIBLE 0x01 +#define SPEED_OVERDRIVE 0x02 + +#define NUM_EP 4 +#define EP_CONTROL 0 +#define EP_STATUS 1 +#define EP_DATA_OUT 2 +#define EP_DATA_IN 3 + +struct ds_device { + struct list_head ds_entry; + + struct usb_device *udev; + struct usb_interface *intf; + + int ep[NUM_EP]; + + /* Strong PullUp + * 0: pullup not active, else duration in milliseconds + */ + int spu_sleep; + /* spu_bit contains COMM_SPU or 0 depending on if the strong pullup + * should be active or not for writes. + */ + u16 spu_bit; + + u8 st_buf[ST_SIZE]; + u8 byte_buf; + + struct w1_bus_master master; +}; + +struct ds_status { + u8 enable; + u8 speed; + u8 pullup_dur; + u8 ppuls_dur; + u8 pulldown_slew; + u8 write1_time; + u8 write0_time; + u8 reserved0; + u8 status; + u8 command0; + u8 command1; + u8 command_buffer_status; + u8 data_out_buffer_status; + u8 data_in_buffer_status; + u8 reserved1; + u8 reserved2; +}; + +static LIST_HEAD(ds_devices); +static DEFINE_MUTEX(ds_mutex); + +static int ds_send_control_cmd(struct ds_device *dev, u16 value, u16 index) +{ + int err; + + err = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, dev->ep[EP_CONTROL]), + CONTROL_CMD, VENDOR, value, index, NULL, 0, 1000); + if (err < 0) { + pr_err("Failed to send command control message %x.%x: err=%d.\n", + value, index, err); + return err; + } + + return err; +} + +static int ds_send_control_mode(struct ds_device *dev, u16 value, u16 index) +{ + int err; + + err = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, dev->ep[EP_CONTROL]), + MODE_CMD, VENDOR, value, index, NULL, 0, 1000); + if (err < 0) { + pr_err("Failed to send mode control message %x.%x: err=%d.\n", + value, index, err); + return err; + } + + return err; +} + +static int ds_send_control(struct ds_device *dev, u16 value, u16 index) +{ + int err; + + err = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, dev->ep[EP_CONTROL]), + COMM_CMD, VENDOR, value, index, NULL, 0, 1000); + if (err < 0) { + pr_err("Failed to send control message %x.%x: err=%d.\n", + value, index, err); + return err; + } + + return err; +} + +static inline void ds_print_msg(unsigned char *buf, unsigned char *str, int off) +{ + pr_info("%45s: %8x\n", str, buf[off]); +} + +static void ds_dump_status(struct ds_device *dev, unsigned char *buf, int count) +{ + int i; + + pr_info("0x%x: count=%d, status: ", dev->ep[EP_STATUS], count); + for (i = 0; i < count; ++i) + pr_info("%02x ", buf[i]); + pr_info("\n"); + + if (count >= 16) { + ds_print_msg(buf, "enable flag", 0); + ds_print_msg(buf, "1-wire speed", 1); + ds_print_msg(buf, "strong pullup duration", 2); + ds_print_msg(buf, "programming pulse duration", 3); + ds_print_msg(buf, "pulldown slew rate control", 4); + ds_print_msg(buf, "write-1 low time", 5); + ds_print_msg(buf, "data sample offset/write-0 recovery time", + 6); + ds_print_msg(buf, "reserved (test register)", 7); + ds_print_msg(buf, "device status flags", 8); + ds_print_msg(buf, "communication command byte 1", 9); + ds_print_msg(buf, "communication command byte 2", 10); + ds_print_msg(buf, "communication command buffer status", 11); + ds_print_msg(buf, "1-wire data output buffer status", 12); + ds_print_msg(buf, "1-wire data input buffer status", 13); + ds_print_msg(buf, "reserved", 14); + ds_print_msg(buf, "reserved", 15); + } + for (i = 16; i < count; ++i) { + if (buf[i] == RR_DETECT) { + ds_print_msg(buf, "new device detect", i); + continue; + } + ds_print_msg(buf, "Result Register Value: ", i); + if (buf[i] & RR_NRS) + pr_info("NRS: Reset no presence or ...\n"); + if (buf[i] & RR_SH) + pr_info("SH: short on reset or set path\n"); + if (buf[i] & RR_APP) + pr_info("APP: alarming presence on reset\n"); + if (buf[i] & RR_VPP) + pr_info("VPP: 12V expected not seen\n"); + if (buf[i] & RR_CMP) + pr_info("CMP: compare error\n"); + if (buf[i] & RR_CRC) + pr_info("CRC: CRC error detected\n"); + if (buf[i] & RR_RDP) + pr_info("RDP: redirected page\n"); + if (buf[i] & RR_EOS) + pr_info("EOS: end of search error\n"); + } +} + +static int ds_recv_status(struct ds_device *dev, struct ds_status *st, + bool dump) +{ + int count, err; + + if (st) + memset(st, 0, sizeof(*st)); + + count = 0; + err = usb_interrupt_msg(dev->udev, + usb_rcvintpipe(dev->udev, + dev->ep[EP_STATUS]), + dev->st_buf, sizeof(dev->st_buf), + &count, 1000); + if (err < 0) { + pr_err("Failed to read 1-wire data from 0x%x: err=%d.\n", + dev->ep[EP_STATUS], err); + return err; + } + + if (dump) + ds_dump_status(dev, dev->st_buf, count); + + if (st && count >= sizeof(*st)) + memcpy(st, dev->st_buf, sizeof(*st)); + + return count; +} + +static void ds_reset_device(struct ds_device *dev) +{ + ds_send_control_cmd(dev, CTL_RESET_DEVICE, 0); + /* Always allow strong pullup which allow individual writes to use + * the strong pullup. + */ + if (ds_send_control_mode(dev, MOD_PULSE_EN, PULSE_SPUE)) + pr_err("ds_reset_device: Error allowing strong pullup\n"); + /* Chip strong pullup time was cleared. */ + if (dev->spu_sleep) { + /* lower 4 bits are 0, see ds_set_pullup */ + u8 del = dev->spu_sleep>>4; + if (ds_send_control(dev, COMM_SET_DURATION | COMM_IM, del)) + pr_err("ds_reset_device: Error setting duration\n"); + } +} + +static int ds_recv_data(struct ds_device *dev, unsigned char *buf, int size) +{ + int count, err; + + /* Careful on size. If size is less than what is available in + * the input buffer, the device fails the bulk transfer and + * clears the input buffer. It could read the maximum size of + * the data buffer, but then do you return the first, last, or + * some set of the middle size bytes? As long as the rest of + * the code is correct there will be size bytes waiting. A + * call to ds_wait_status will wait until the device is idle + * and any data to be received would have been available. + */ + count = 0; + err = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, dev->ep[EP_DATA_IN]), + buf, size, &count, 1000); + if (err < 0) { + pr_info("Clearing ep0x%x.\n", dev->ep[EP_DATA_IN]); + usb_clear_halt(dev->udev, usb_rcvbulkpipe(dev->udev, dev->ep[EP_DATA_IN])); + ds_recv_status(dev, NULL, true); + return err; + } + +#if 0 + { + int i; + + printk("%s: count=%d: ", __func__, count); + for (i = 0; i < count; ++i) + printk("%02x ", buf[i]); + printk("\n"); + } +#endif + return count; +} + +static int ds_send_data(struct ds_device *dev, unsigned char *buf, int len) +{ + int count, err; + + count = 0; + err = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, dev->ep[EP_DATA_OUT]), buf, len, &count, 1000); + if (err < 0) { + pr_err("Failed to write 1-wire data to ep0x%x: " + "err=%d.\n", dev->ep[EP_DATA_OUT], err); + return err; + } + + return err; +} + +#if 0 + +int ds_stop_pulse(struct ds_device *dev, int limit) +{ + struct ds_status st; + int count = 0, err = 0; + + do { + err = ds_send_control(dev, CTL_HALT_EXE_IDLE, 0); + if (err) + break; + err = ds_send_control(dev, CTL_RESUME_EXE, 0); + if (err) + break; + err = ds_recv_status(dev, &st, false); + if (err) + break; + + if ((st.status & ST_SPUA) == 0) { + err = ds_send_control_mode(dev, MOD_PULSE_EN, 0); + if (err) + break; + } + } while (++count < limit); + + return err; +} + +int ds_detect(struct ds_device *dev, struct ds_status *st) +{ + int err; + + err = ds_send_control_cmd(dev, CTL_RESET_DEVICE, 0); + if (err) + return err; + + err = ds_send_control(dev, COMM_SET_DURATION | COMM_IM, 0); + if (err) + return err; + + err = ds_send_control(dev, COMM_SET_DURATION | COMM_IM | COMM_TYPE, 0x40); + if (err) + return err; + + err = ds_send_control_mode(dev, MOD_PULSE_EN, PULSE_PROG); + if (err) + return err; + + err = ds_dump_status(dev, st); + + return err; +} + +#endif /* 0 */ + +static int ds_wait_status(struct ds_device *dev, struct ds_status *st) +{ + int err, count = 0; + + do { + st->status = 0; + err = ds_recv_status(dev, st, false); +#if 0 + if (err >= 0) { + int i; + printk("0x%x: count=%d, status: ", dev->ep[EP_STATUS], err); + for (i = 0; i < err; ++i) + printk("%02x ", dev->st_buf[i]); + printk("\n"); + } +#endif + } while (!(st->status & ST_IDLE) && !(err < 0) && ++count < 100); + + if (err >= 16 && st->status & ST_EPOF) { + pr_info("Resetting device after ST_EPOF.\n"); + ds_reset_device(dev); + /* Always dump the device status. */ + count = 101; + } + + /* Dump the status for errors or if there is extended return data. + * The extended status includes new device detection (maybe someone + * can do something with it). + */ + if (err > 16 || count >= 100 || err < 0) + ds_dump_status(dev, dev->st_buf, err); + + /* Extended data isn't an error. Well, a short is, but the dump + * would have already told the user that and we can't do anything + * about it in software anyway. + */ + if (count >= 100 || err < 0) + return -1; + else + return 0; +} + +static int ds_reset(struct ds_device *dev) +{ + int err; + + /* Other potentionally interesting flags for reset. + * + * COMM_NTF: Return result register feedback. This could be used to + * detect some conditions such as short, alarming presence, or + * detect if a new device was detected. + * + * COMM_SE which allows SPEED_NORMAL, SPEED_FLEXIBLE, SPEED_OVERDRIVE: + * Select the data transfer rate. + */ + err = ds_send_control(dev, COMM_1_WIRE_RESET | COMM_IM, SPEED_NORMAL); + if (err) + return err; + + return 0; +} + +#if 0 +static int ds_set_speed(struct ds_device *dev, int speed) +{ + int err; + + if (speed != SPEED_NORMAL && speed != SPEED_FLEXIBLE && speed != SPEED_OVERDRIVE) + return -EINVAL; + + if (speed != SPEED_OVERDRIVE) + speed = SPEED_FLEXIBLE; + + speed &= 0xff; + + err = ds_send_control_mode(dev, MOD_1WIRE_SPEED, speed); + if (err) + return err; + + return err; +} +#endif /* 0 */ + +static int ds_set_pullup(struct ds_device *dev, int delay) +{ + int err = 0; + u8 del = 1 + (u8)(delay >> 4); + /* Just storing delay would not get the trunication and roundup. */ + int ms = del<<4; + + /* Enable spu_bit if a delay is set. */ + dev->spu_bit = delay ? COMM_SPU : 0; + /* If delay is zero, it has already been disabled, if the time is + * the same as the hardware was last programmed to, there is also + * nothing more to do. Compare with the recalculated value ms + * rather than del or delay which can have a different value. + */ + if (delay == 0 || ms == dev->spu_sleep) + return err; + + err = ds_send_control(dev, COMM_SET_DURATION | COMM_IM, del); + if (err) + return err; + + dev->spu_sleep = ms; + + return err; +} + +static int ds_touch_bit(struct ds_device *dev, u8 bit, u8 *tbit) +{ + int err; + struct ds_status st; + + err = ds_send_control(dev, COMM_BIT_IO | COMM_IM | (bit ? COMM_D : 0), + 0); + if (err) + return err; + + ds_wait_status(dev, &st); + + err = ds_recv_data(dev, tbit, sizeof(*tbit)); + if (err < 0) + return err; + + return 0; +} + +#if 0 +static int ds_write_bit(struct ds_device *dev, u8 bit) +{ + int err; + struct ds_status st; + + /* Set COMM_ICP to write without a readback. Note, this will + * produce one time slot, a down followed by an up with COMM_D + * only determing the timing. + */ + err = ds_send_control(dev, COMM_BIT_IO | COMM_IM | COMM_ICP | + (bit ? COMM_D : 0), 0); + if (err) + return err; + + ds_wait_status(dev, &st); + + return 0; +} +#endif + +static int ds_write_byte(struct ds_device *dev, u8 byte) +{ + int err; + struct ds_status st; + + err = ds_send_control(dev, COMM_BYTE_IO | COMM_IM | dev->spu_bit, byte); + if (err) + return err; + + if (dev->spu_bit) + msleep(dev->spu_sleep); + + err = ds_wait_status(dev, &st); + if (err) + return err; + + err = ds_recv_data(dev, &dev->byte_buf, 1); + if (err < 0) + return err; + + return !(byte == dev->byte_buf); +} + +static int ds_read_byte(struct ds_device *dev, u8 *byte) +{ + int err; + struct ds_status st; + + err = ds_send_control(dev, COMM_BYTE_IO | COMM_IM, 0xff); + if (err) + return err; + + ds_wait_status(dev, &st); + + err = ds_recv_data(dev, byte, sizeof(*byte)); + if (err < 0) + return err; + + return 0; +} + +static int ds_read_block(struct ds_device *dev, u8 *buf, int len) +{ + struct ds_status st; + int err; + + if (len > 64*1024) + return -E2BIG; + + memset(buf, 0xFF, len); + + err = ds_send_data(dev, buf, len); + if (err < 0) + return err; + + err = ds_send_control(dev, COMM_BLOCK_IO | COMM_IM, len); + if (err) + return err; + + ds_wait_status(dev, &st); + + memset(buf, 0x00, len); + err = ds_recv_data(dev, buf, len); + + return err; +} + +static int ds_write_block(struct ds_device *dev, u8 *buf, int len) +{ + int err; + struct ds_status st; + + err = ds_send_data(dev, buf, len); + if (err < 0) + return err; + + err = ds_send_control(dev, COMM_BLOCK_IO | COMM_IM | dev->spu_bit, len); + if (err) + return err; + + if (dev->spu_bit) + msleep(dev->spu_sleep); + + ds_wait_status(dev, &st); + + err = ds_recv_data(dev, buf, len); + if (err < 0) + return err; + + return !(err == len); +} + +static void ds9490r_search(void *data, struct w1_master *master, + u8 search_type, w1_slave_found_callback callback) +{ + /* When starting with an existing id, the first id returned will + * be that device (if it is still on the bus most likely). + * + * If the number of devices found is less than or equal to the + * search_limit, that number of IDs will be returned. If there are + * more, search_limit IDs will be returned followed by a non-zero + * discrepency value. + */ + struct ds_device *dev = data; + int err; + u16 value, index; + struct ds_status st; + int search_limit; + int found = 0; + int i; + + /* DS18b20 spec, 13.16 ms per device, 75 per second, sleep for + * discovering 8 devices (1 bulk transfer and 1/2 FIFO size) at a time. + */ + const unsigned long jtime = msecs_to_jiffies(1000*8/75); + /* FIFO 128 bytes, bulk packet size 64, read a multiple of the + * packet size. + */ + const size_t bufsize = 2 * 64; + u64 *buf; + + buf = kmalloc(bufsize, GFP_KERNEL); + if (!buf) + return; + + mutex_lock(&master->bus_mutex); + + /* address to start searching at */ + if (ds_send_data(dev, (u8 *)&master->search_id, 8) < 0) + goto search_out; + master->search_id = 0; + + value = COMM_SEARCH_ACCESS | COMM_IM | COMM_RST | COMM_SM | COMM_F | + COMM_RTS; + search_limit = master->max_slave_count; + if (search_limit > 255) + search_limit = 0; + index = search_type | (search_limit << 8); + if (ds_send_control(dev, value, index) < 0) + goto search_out; + + do { + schedule_timeout(jtime); + + err = ds_recv_status(dev, &st, false); + if (err < 0 || err < sizeof(st)) + break; + + if (st.data_in_buffer_status) { + /* Bulk in can receive partial ids, but when it does + * they fail crc and will be discarded anyway. + * That has only been seen when status in buffer + * is 0 and bulk is read anyway, so don't read + * bulk without first checking if status says there + * is data to read. + */ + err = ds_recv_data(dev, (u8 *)buf, bufsize); + if (err < 0) + break; + for (i = 0; i < err/8; ++i) { + ++found; + if (found <= search_limit) + callback(master, buf[i]); + /* can't know if there will be a discrepancy + * value after until the next id */ + if (found == search_limit) + master->search_id = buf[i]; + } + } + + if (test_bit(W1_ABORT_SEARCH, &master->flags)) + break; + } while (!(st.status & (ST_IDLE | ST_HALT))); + + /* only continue the search if some weren't found */ + if (found <= search_limit) { + master->search_id = 0; + } else if (!test_bit(W1_WARN_MAX_COUNT, &master->flags)) { + /* Only max_slave_count will be scanned in a search, + * but it will start where it left off next search + * until all ids are identified and then it will start + * over. A continued search will report the previous + * last id as the first id (provided it is still on the + * bus). + */ + dev_info(&dev->udev->dev, "%s: max_slave_count %d reached, " + "will continue next search.\n", __func__, + master->max_slave_count); + set_bit(W1_WARN_MAX_COUNT, &master->flags); + } +search_out: + mutex_unlock(&master->bus_mutex); + kfree(buf); +} + +#if 0 +/* + * FIXME: if this disabled code is ever used in the future all ds_send_data() + * calls must be changed to use a DMAable buffer. + */ +static int ds_match_access(struct ds_device *dev, u64 init) +{ + int err; + struct ds_status st; + + err = ds_send_data(dev, (unsigned char *)&init, sizeof(init)); + if (err) + return err; + + ds_wait_status(dev, &st); + + err = ds_send_control(dev, COMM_MATCH_ACCESS | COMM_IM | COMM_RST, 0x0055); + if (err) + return err; + + ds_wait_status(dev, &st); + + return 0; +} + +static int ds_set_path(struct ds_device *dev, u64 init) +{ + int err; + struct ds_status st; + u8 buf[9]; + + memcpy(buf, &init, 8); + buf[8] = BRANCH_MAIN; + + err = ds_send_data(dev, buf, sizeof(buf)); + if (err) + return err; + + ds_wait_status(dev, &st); + + err = ds_send_control(dev, COMM_SET_PATH | COMM_IM | COMM_RST, 0); + if (err) + return err; + + ds_wait_status(dev, &st); + + return 0; +} + +#endif /* 0 */ + +static u8 ds9490r_touch_bit(void *data, u8 bit) +{ + struct ds_device *dev = data; + + if (ds_touch_bit(dev, bit, &dev->byte_buf)) + return 0; + + return dev->byte_buf; +} + +#if 0 +static void ds9490r_write_bit(void *data, u8 bit) +{ + struct ds_device *dev = data; + + ds_write_bit(dev, bit); +} + +static u8 ds9490r_read_bit(void *data) +{ + struct ds_device *dev = data; + int err; + + err = ds_touch_bit(dev, 1, &dev->byte_buf); + if (err) + return 0; + + return dev->byte_buf & 1; +} +#endif + +static void ds9490r_write_byte(void *data, u8 byte) +{ + struct ds_device *dev = data; + + ds_write_byte(dev, byte); +} + +static u8 ds9490r_read_byte(void *data) +{ + struct ds_device *dev = data; + int err; + + err = ds_read_byte(dev, &dev->byte_buf); + if (err) + return 0; + + return dev->byte_buf; +} + +static void ds9490r_write_block(void *data, const u8 *buf, int len) +{ + struct ds_device *dev = data; + u8 *tbuf; + + if (len <= 0) + return; + + tbuf = kmemdup(buf, len, GFP_KERNEL); + if (!tbuf) + return; + + ds_write_block(dev, tbuf, len); + + kfree(tbuf); +} + +static u8 ds9490r_read_block(void *data, u8 *buf, int len) +{ + struct ds_device *dev = data; + int err; + u8 *tbuf; + + if (len <= 0) + return 0; + + tbuf = kmalloc(len, GFP_KERNEL); + if (!tbuf) + return 0; + + err = ds_read_block(dev, tbuf, len); + if (err >= 0) + memcpy(buf, tbuf, len); + + kfree(tbuf); + + return err >= 0 ? len : 0; +} + +static u8 ds9490r_reset(void *data) +{ + struct ds_device *dev = data; + int err; + + err = ds_reset(dev); + if (err) + return 1; + + return 0; +} + +static u8 ds9490r_set_pullup(void *data, int delay) +{ + struct ds_device *dev = data; + + if (ds_set_pullup(dev, delay)) + return 1; + + return 0; +} + +static int ds_w1_init(struct ds_device *dev) +{ + memset(&dev->master, 0, sizeof(struct w1_bus_master)); + + /* Reset the device as it can be in a bad state. + * This is necessary because a block write will wait for data + * to be placed in the output buffer and block any later + * commands which will keep accumulating and the device will + * not be idle. Another case is removing the ds2490 module + * while a bus search is in progress, somehow a few commands + * get through, but the input transfers fail leaving data in + * the input buffer. This will cause the next read to fail + * see the note in ds_recv_data. + */ + ds_reset_device(dev); + + dev->master.data = dev; + dev->master.touch_bit = &ds9490r_touch_bit; + /* read_bit and write_bit in w1_bus_master are expected to set and + * sample the line level. For write_bit that means it is expected to + * set it to that value and leave it there. ds2490 only supports an + * individual time slot at the lowest level. The requirement from + * pulling the bus state down to reading the state is 15us, something + * that isn't realistic on the USB bus anyway. + dev->master.read_bit = &ds9490r_read_bit; + dev->master.write_bit = &ds9490r_write_bit; + */ + dev->master.read_byte = &ds9490r_read_byte; + dev->master.write_byte = &ds9490r_write_byte; + dev->master.read_block = &ds9490r_read_block; + dev->master.write_block = &ds9490r_write_block; + dev->master.reset_bus = &ds9490r_reset; + dev->master.set_pullup = &ds9490r_set_pullup; + dev->master.search = &ds9490r_search; + + return w1_add_master_device(&dev->master); +} + +static void ds_w1_fini(struct ds_device *dev) +{ + w1_remove_master_device(&dev->master); +} + +static int ds_probe(struct usb_interface *intf, + const struct usb_device_id *udev_id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *endpoint; + struct usb_host_interface *iface_desc; + struct ds_device *dev; + int i, err, alt; + + dev = kzalloc(sizeof(struct ds_device), GFP_KERNEL); + if (!dev) { + pr_info("Failed to allocate new DS9490R structure.\n"); + return -ENOMEM; + } + dev->udev = usb_get_dev(udev); + if (!dev->udev) { + err = -ENOMEM; + goto err_out_free; + } + memset(dev->ep, 0, sizeof(dev->ep)); + + usb_set_intfdata(intf, dev); + + err = usb_reset_configuration(dev->udev); + if (err) { + dev_err(&dev->udev->dev, + "Failed to reset configuration: err=%d.\n", err); + goto err_out_clear; + } + + /* alternative 3, 1ms interrupt (greatly speeds search), 64 byte bulk */ + alt = 3; + err = usb_set_interface(dev->udev, + intf->cur_altsetting->desc.bInterfaceNumber, alt); + if (err) { + dev_err(&dev->udev->dev, "Failed to set alternative setting %d " + "for %d interface: err=%d.\n", alt, + intf->cur_altsetting->desc.bInterfaceNumber, err); + goto err_out_clear; + } + + iface_desc = intf->cur_altsetting; + if (iface_desc->desc.bNumEndpoints != NUM_EP-1) { + pr_info("Num endpoints=%d. It is not DS9490R.\n", + iface_desc->desc.bNumEndpoints); + err = -EINVAL; + goto err_out_clear; + } + + /* + * This loop doesn'd show control 0 endpoint, + * so we will fill only 1-3 endpoints entry. + */ + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + dev->ep[i+1] = endpoint->bEndpointAddress; +#if 0 + printk("%d: addr=%x, size=%d, dir=%s, type=%x\n", + i, endpoint->bEndpointAddress, le16_to_cpu(endpoint->wMaxPacketSize), + (endpoint->bEndpointAddress & USB_DIR_IN)?"IN":"OUT", + endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK); +#endif + } + + err = ds_w1_init(dev); + if (err) + goto err_out_clear; + + mutex_lock(&ds_mutex); + list_add_tail(&dev->ds_entry, &ds_devices); + mutex_unlock(&ds_mutex); + + return 0; + +err_out_clear: + usb_set_intfdata(intf, NULL); + usb_put_dev(dev->udev); +err_out_free: + kfree(dev); + return err; +} + +static void ds_disconnect(struct usb_interface *intf) +{ + struct ds_device *dev; + + dev = usb_get_intfdata(intf); + if (!dev) + return; + + mutex_lock(&ds_mutex); + list_del(&dev->ds_entry); + mutex_unlock(&ds_mutex); + + ds_w1_fini(dev); + + usb_set_intfdata(intf, NULL); + + usb_put_dev(dev->udev); + kfree(dev); +} + +static const struct usb_device_id ds_id_table[] = { + { USB_DEVICE(0x04fa, 0x2490) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, ds_id_table); + +static struct usb_driver ds_driver = { + .name = "DS9490R", + .probe = ds_probe, + .disconnect = ds_disconnect, + .id_table = ds_id_table, +}; +module_usb_driver(ds_driver); + +MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>"); +MODULE_DESCRIPTION("DS2490 USB <-> W1 bus master driver (DS9490*)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/w1/masters/matrox_w1.c b/drivers/w1/masters/matrox_w1.c new file mode 100644 index 000000000..d83d7c99d --- /dev/null +++ b/drivers/w1/masters/matrox_w1.c @@ -0,0 +1,222 @@ +/* + * matrox_w1.c + * + * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <asm/types.h> +#include <linux/atomic.h> +#include <asm/io.h> + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/timer.h> +#include <linux/slab.h> +#include <linux/pci_ids.h> +#include <linux/pci.h> + +#include <linux/w1.h> + +/* + * Matrox G400 DDC registers. + */ + +#define MATROX_G400_DDC_CLK (1<<4) +#define MATROX_G400_DDC_DATA (1<<1) + +#define MATROX_BASE 0x3C00 +#define MATROX_STATUS 0x1e14 + +#define MATROX_PORT_INDEX_OFFSET 0x00 +#define MATROX_PORT_DATA_OFFSET 0x0A + +#define MATROX_GET_CONTROL 0x2A +#define MATROX_GET_DATA 0x2B +#define MATROX_CURSOR_CTL 0x06 + +struct matrox_device +{ + void __iomem *base_addr; + void __iomem *port_index; + void __iomem *port_data; + u8 data_mask; + + unsigned long phys_addr; + void __iomem *virt_addr; + unsigned long found; + + struct w1_bus_master *bus_master; +}; + +/* + * These functions read and write DDC Data bit. + * + * Using tristate pins, since i can't find any open-drain pin in whole motherboard. + * Unfortunately we can't connect to Intel's 82801xx IO controller + * since we don't know motherboard schema, which has pretty unused(may be not) GPIO. + * + * I've heard that PIIX also has open drain pin. + * + * Port mapping. + */ +static __inline__ u8 matrox_w1_read_reg(struct matrox_device *dev, u8 reg) +{ + u8 ret; + + writeb(reg, dev->port_index); + ret = readb(dev->port_data); + barrier(); + + return ret; +} + +static __inline__ void matrox_w1_write_reg(struct matrox_device *dev, u8 reg, u8 val) +{ + writeb(reg, dev->port_index); + writeb(val, dev->port_data); + wmb(); +} + +static void matrox_w1_write_ddc_bit(void *data, u8 bit) +{ + u8 ret; + struct matrox_device *dev = data; + + if (bit) + bit = 0; + else + bit = dev->data_mask; + + ret = matrox_w1_read_reg(dev, MATROX_GET_CONTROL); + matrox_w1_write_reg(dev, MATROX_GET_CONTROL, ((ret & ~dev->data_mask) | bit)); + matrox_w1_write_reg(dev, MATROX_GET_DATA, 0x00); +} + +static u8 matrox_w1_read_ddc_bit(void *data) +{ + u8 ret; + struct matrox_device *dev = data; + + ret = matrox_w1_read_reg(dev, MATROX_GET_DATA); + + return ret; +} + +static void matrox_w1_hw_init(struct matrox_device *dev) +{ + matrox_w1_write_reg(dev, MATROX_GET_DATA, 0xFF); + matrox_w1_write_reg(dev, MATROX_GET_CONTROL, 0x00); +} + +static int matrox_w1_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct matrox_device *dev; + int err; + + if (pdev->vendor != PCI_VENDOR_ID_MATROX || pdev->device != PCI_DEVICE_ID_MATROX_G400) + return -ENODEV; + + dev = kzalloc(sizeof(struct matrox_device) + + sizeof(struct w1_bus_master), GFP_KERNEL); + if (!dev) { + dev_err(&pdev->dev, + "%s: Failed to create new matrox_device object.\n", + __func__); + return -ENOMEM; + } + + + dev->bus_master = (struct w1_bus_master *)(dev + 1); + + /* + * True for G400, for some other we need resource 0, see drivers/video/matrox/matroxfb_base.c + */ + + dev->phys_addr = pci_resource_start(pdev, 1); + + dev->virt_addr = ioremap_nocache(dev->phys_addr, 16384); + if (!dev->virt_addr) { + dev_err(&pdev->dev, "%s: failed to ioremap(0x%lx, %d).\n", + __func__, dev->phys_addr, 16384); + err = -EIO; + goto err_out_free_device; + } + + dev->base_addr = dev->virt_addr + MATROX_BASE; + dev->port_index = dev->base_addr + MATROX_PORT_INDEX_OFFSET; + dev->port_data = dev->base_addr + MATROX_PORT_DATA_OFFSET; + dev->data_mask = (MATROX_G400_DDC_DATA); + + matrox_w1_hw_init(dev); + + dev->bus_master->data = dev; + dev->bus_master->read_bit = &matrox_w1_read_ddc_bit; + dev->bus_master->write_bit = &matrox_w1_write_ddc_bit; + + err = w1_add_master_device(dev->bus_master); + if (err) + goto err_out_free_device; + + pci_set_drvdata(pdev, dev); + + dev->found = 1; + + dev_info(&pdev->dev, "Matrox G400 GPIO transport layer for 1-wire.\n"); + + return 0; + +err_out_free_device: + if (dev->virt_addr) + iounmap(dev->virt_addr); + kfree(dev); + + return err; +} + +static void matrox_w1_remove(struct pci_dev *pdev) +{ + struct matrox_device *dev = pci_get_drvdata(pdev); + + if (dev->found) { + w1_remove_master_device(dev->bus_master); + iounmap(dev->virt_addr); + } + kfree(dev); +} + +static struct pci_device_id matrox_w1_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G400) }, + { }, +}; +MODULE_DEVICE_TABLE(pci, matrox_w1_tbl); + +static struct pci_driver matrox_w1_pci_driver = { + .name = "matrox_w1", + .id_table = matrox_w1_tbl, + .probe = matrox_w1_probe, + .remove = matrox_w1_remove, +}; +module_pci_driver(matrox_w1_pci_driver); + +MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>"); +MODULE_DESCRIPTION("Driver for transport(Dallas 1-wire protocol) over VGA DDC(matrox gpio)."); +MODULE_LICENSE("GPL"); diff --git a/drivers/w1/masters/mxc_w1.c b/drivers/w1/masters/mxc_w1.c new file mode 100644 index 000000000..075454053 --- /dev/null +++ b/drivers/w1/masters/mxc_w1.c @@ -0,0 +1,193 @@ +/* + * Copyright 2005-2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Luotao Fu, kernel@pengutronix.de + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/ktime.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> + +#include <linux/w1.h> + +/* + * MXC W1 Register offsets + */ +#define MXC_W1_CONTROL 0x00 +# define MXC_W1_CONTROL_RDST BIT(3) +# define MXC_W1_CONTROL_WR(x) BIT(5 - (x)) +# define MXC_W1_CONTROL_PST BIT(6) +# define MXC_W1_CONTROL_RPP BIT(7) +#define MXC_W1_TIME_DIVIDER 0x02 +#define MXC_W1_RESET 0x04 +# define MXC_W1_RESET_RST BIT(0) + +struct mxc_w1_device { + void __iomem *regs; + struct clk *clk; + struct w1_bus_master bus_master; +}; + +/* + * this is the low level routine to + * reset the device on the One Wire interface + * on the hardware + */ +static u8 mxc_w1_ds2_reset_bus(void *data) +{ + struct mxc_w1_device *dev = data; + ktime_t timeout; + + writeb(MXC_W1_CONTROL_RPP, dev->regs + MXC_W1_CONTROL); + + /* Wait for reset sequence 511+512us, use 1500us for sure */ + timeout = ktime_add_us(ktime_get(), 1500); + + udelay(511 + 512); + + do { + u8 ctrl = readb(dev->regs + MXC_W1_CONTROL); + + /* PST bit is valid after the RPP bit is self-cleared */ + if (!(ctrl & MXC_W1_CONTROL_RPP)) + return !(ctrl & MXC_W1_CONTROL_PST); + } while (ktime_before(ktime_get(), timeout)); + + return 1; +} + +/* + * this is the low level routine to read/write a bit on the One Wire + * interface on the hardware. It does write 0 if parameter bit is set + * to 0, otherwise a write 1/read. + */ +static u8 mxc_w1_ds2_touch_bit(void *data, u8 bit) +{ + struct mxc_w1_device *dev = data; + ktime_t timeout; + + writeb(MXC_W1_CONTROL_WR(bit), dev->regs + MXC_W1_CONTROL); + + /* Wait for read/write bit (60us, Max 120us), use 200us for sure */ + timeout = ktime_add_us(ktime_get(), 200); + + udelay(60); + + do { + u8 ctrl = readb(dev->regs + MXC_W1_CONTROL); + + /* RDST bit is valid after the WR1/RD bit is self-cleared */ + if (!(ctrl & MXC_W1_CONTROL_WR(bit))) + return !!(ctrl & MXC_W1_CONTROL_RDST); + } while (ktime_before(ktime_get(), timeout)); + + return 0; +} + +static int mxc_w1_probe(struct platform_device *pdev) +{ + struct mxc_w1_device *mdev; + unsigned long clkrate; + struct resource *res; + unsigned int clkdiv; + int err; + + mdev = devm_kzalloc(&pdev->dev, sizeof(struct mxc_w1_device), + GFP_KERNEL); + if (!mdev) + return -ENOMEM; + + mdev->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mdev->clk)) + return PTR_ERR(mdev->clk); + + err = clk_prepare_enable(mdev->clk); + if (err) + return err; + + clkrate = clk_get_rate(mdev->clk); + if (clkrate < 10000000) + dev_warn(&pdev->dev, + "Low clock frequency causes improper function\n"); + + clkdiv = DIV_ROUND_CLOSEST(clkrate, 1000000); + clkrate /= clkdiv; + if ((clkrate < 980000) || (clkrate > 1020000)) + dev_warn(&pdev->dev, + "Incorrect time base frequency %lu Hz\n", clkrate); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mdev->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mdev->regs)) { + err = PTR_ERR(mdev->regs); + goto out_disable_clk; + } + + /* Software reset 1-Wire module */ + writeb(MXC_W1_RESET_RST, mdev->regs + MXC_W1_RESET); + writeb(0, mdev->regs + MXC_W1_RESET); + + writeb(clkdiv - 1, mdev->regs + MXC_W1_TIME_DIVIDER); + + mdev->bus_master.data = mdev; + mdev->bus_master.reset_bus = mxc_w1_ds2_reset_bus; + mdev->bus_master.touch_bit = mxc_w1_ds2_touch_bit; + + platform_set_drvdata(pdev, mdev); + + err = w1_add_master_device(&mdev->bus_master); + if (err) + goto out_disable_clk; + + return 0; + +out_disable_clk: + clk_disable_unprepare(mdev->clk); + return err; +} + +/* + * disassociate the w1 device from the driver + */ +static int mxc_w1_remove(struct platform_device *pdev) +{ + struct mxc_w1_device *mdev = platform_get_drvdata(pdev); + + w1_remove_master_device(&mdev->bus_master); + + clk_disable_unprepare(mdev->clk); + + return 0; +} + +static const struct of_device_id mxc_w1_dt_ids[] = { + { .compatible = "fsl,imx21-owire" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxc_w1_dt_ids); + +static struct platform_driver mxc_w1_driver = { + .driver = { + .name = "mxc_w1", + .of_match_table = mxc_w1_dt_ids, + }, + .probe = mxc_w1_probe, + .remove = mxc_w1_remove, +}; +module_platform_driver(mxc_w1_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductors Inc"); +MODULE_DESCRIPTION("Driver for One-Wire on MXC"); diff --git a/drivers/w1/masters/omap_hdq.c b/drivers/w1/masters/omap_hdq.c new file mode 100644 index 000000000..0667bc6e7 --- /dev/null +++ b/drivers/w1/masters/omap_hdq.c @@ -0,0 +1,790 @@ +/* + * drivers/w1/masters/omap_hdq.c + * + * Copyright (C) 2007,2012 Texas Instruments, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/sched.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> + +#include <linux/w1.h> + +#define MOD_NAME "OMAP_HDQ:" + +#define OMAP_HDQ_REVISION 0x00 +#define OMAP_HDQ_TX_DATA 0x04 +#define OMAP_HDQ_RX_DATA 0x08 +#define OMAP_HDQ_CTRL_STATUS 0x0c +#define OMAP_HDQ_CTRL_STATUS_SINGLE BIT(7) +#define OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK BIT(6) +#define OMAP_HDQ_CTRL_STATUS_CLOCKENABLE BIT(5) +#define OMAP_HDQ_CTRL_STATUS_GO BIT(4) +#define OMAP_HDQ_CTRL_STATUS_PRESENCE BIT(3) +#define OMAP_HDQ_CTRL_STATUS_INITIALIZATION BIT(2) +#define OMAP_HDQ_CTRL_STATUS_DIR BIT(1) +#define OMAP_HDQ_INT_STATUS 0x10 +#define OMAP_HDQ_INT_STATUS_TXCOMPLETE BIT(2) +#define OMAP_HDQ_INT_STATUS_RXCOMPLETE BIT(1) +#define OMAP_HDQ_INT_STATUS_TIMEOUT BIT(0) +#define OMAP_HDQ_SYSCONFIG 0x14 +#define OMAP_HDQ_SYSCONFIG_SOFTRESET BIT(1) +#define OMAP_HDQ_SYSCONFIG_AUTOIDLE BIT(0) +#define OMAP_HDQ_SYSCONFIG_NOIDLE 0x0 +#define OMAP_HDQ_SYSSTATUS 0x18 +#define OMAP_HDQ_SYSSTATUS_RESETDONE BIT(0) + +#define OMAP_HDQ_FLAG_CLEAR 0 +#define OMAP_HDQ_FLAG_SET 1 +#define OMAP_HDQ_TIMEOUT (HZ/5) + +#define OMAP_HDQ_MAX_USER 4 + +static DECLARE_WAIT_QUEUE_HEAD(hdq_wait_queue); + +static int w1_id; +module_param(w1_id, int, S_IRUSR); +MODULE_PARM_DESC(w1_id, "1-wire id for the slave detection in HDQ mode"); + +struct hdq_data { + struct device *dev; + void __iomem *hdq_base; + /* lock status update */ + struct mutex hdq_mutex; + int hdq_usecount; + u8 hdq_irqstatus; + /* device lock */ + spinlock_t hdq_spinlock; + /* + * Used to control the call to omap_hdq_get and omap_hdq_put. + * HDQ Protocol: Write the CMD|REG_address first, followed by + * the data wrire or read. + */ + int init_trans; + int rrw; + /* mode: 0-HDQ 1-W1 */ + int mode; + +}; + +/* HDQ register I/O routines */ +static inline u8 hdq_reg_in(struct hdq_data *hdq_data, u32 offset) +{ + return __raw_readl(hdq_data->hdq_base + offset); +} + +static inline void hdq_reg_out(struct hdq_data *hdq_data, u32 offset, u8 val) +{ + __raw_writel(val, hdq_data->hdq_base + offset); +} + +static inline u8 hdq_reg_merge(struct hdq_data *hdq_data, u32 offset, + u8 val, u8 mask) +{ + u8 new_val = (__raw_readl(hdq_data->hdq_base + offset) & ~mask) + | (val & mask); + __raw_writel(new_val, hdq_data->hdq_base + offset); + + return new_val; +} + +static void hdq_disable_interrupt(struct hdq_data *hdq_data, u32 offset, + u32 mask) +{ + u32 ie; + + ie = readl(hdq_data->hdq_base + offset); + writel(ie & mask, hdq_data->hdq_base + offset); +} + +/* + * Wait for one or more bits in flag change. + * HDQ_FLAG_SET: wait until any bit in the flag is set. + * HDQ_FLAG_CLEAR: wait until all bits in the flag are cleared. + * return 0 on success and -ETIMEDOUT in the case of timeout. + */ +static int hdq_wait_for_flag(struct hdq_data *hdq_data, u32 offset, + u8 flag, u8 flag_set, u8 *status) +{ + int ret = 0; + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT; + + if (flag_set == OMAP_HDQ_FLAG_CLEAR) { + /* wait for the flag clear */ + while (((*status = hdq_reg_in(hdq_data, offset)) & flag) + && time_before(jiffies, timeout)) { + schedule_timeout_uninterruptible(1); + } + if (*status & flag) + ret = -ETIMEDOUT; + } else if (flag_set == OMAP_HDQ_FLAG_SET) { + /* wait for the flag set */ + while (!((*status = hdq_reg_in(hdq_data, offset)) & flag) + && time_before(jiffies, timeout)) { + schedule_timeout_uninterruptible(1); + } + if (!(*status & flag)) + ret = -ETIMEDOUT; + } else + return -EINVAL; + + return ret; +} + +/* write out a byte and fill *status with HDQ_INT_STATUS */ +static int hdq_write_byte(struct hdq_data *hdq_data, u8 val, u8 *status) +{ + int ret; + u8 tmp_status; + unsigned long irqflags; + + *status = 0; + + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags); + /* clear interrupt flags via a dummy read */ + hdq_reg_in(hdq_data, OMAP_HDQ_INT_STATUS); + /* ISR loads it with new INT_STATUS */ + hdq_data->hdq_irqstatus = 0; + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags); + + hdq_reg_out(hdq_data, OMAP_HDQ_TX_DATA, val); + + /* set the GO bit */ + hdq_reg_merge(hdq_data, OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO, + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO); + /* wait for the TXCOMPLETE bit */ + ret = wait_event_timeout(hdq_wait_queue, + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT); + if (ret == 0) { + dev_dbg(hdq_data->dev, "TX wait elapsed\n"); + ret = -ETIMEDOUT; + goto out; + } + + *status = hdq_data->hdq_irqstatus; + /* check irqstatus */ + if (!(*status & OMAP_HDQ_INT_STATUS_TXCOMPLETE)) { + dev_dbg(hdq_data->dev, "timeout waiting for" + " TXCOMPLETE/RXCOMPLETE, %x\n", *status); + ret = -ETIMEDOUT; + goto out; + } + + /* wait for the GO bit return to zero */ + ret = hdq_wait_for_flag(hdq_data, OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_GO, + OMAP_HDQ_FLAG_CLEAR, &tmp_status); + if (ret) { + dev_dbg(hdq_data->dev, "timeout waiting GO bit" + " return to zero, %x\n", tmp_status); + } + +out: + return ret; +} + +/* HDQ Interrupt service routine */ +static irqreturn_t hdq_isr(int irq, void *_hdq) +{ + struct hdq_data *hdq_data = _hdq; + unsigned long irqflags; + + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags); + hdq_data->hdq_irqstatus = hdq_reg_in(hdq_data, OMAP_HDQ_INT_STATUS); + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags); + dev_dbg(hdq_data->dev, "hdq_isr: %x\n", hdq_data->hdq_irqstatus); + + if (hdq_data->hdq_irqstatus & + (OMAP_HDQ_INT_STATUS_TXCOMPLETE | OMAP_HDQ_INT_STATUS_RXCOMPLETE + | OMAP_HDQ_INT_STATUS_TIMEOUT)) { + /* wake up sleeping process */ + wake_up(&hdq_wait_queue); + } + + return IRQ_HANDLED; +} + +/* W1 search callback function in HDQ mode */ +static void omap_w1_search_bus(void *_hdq, struct w1_master *master_dev, + u8 search_type, w1_slave_found_callback slave_found) +{ + u64 module_id, rn_le, cs, id; + + if (w1_id) + module_id = w1_id; + else + module_id = 0x1; + + rn_le = cpu_to_le64(module_id); + /* + * HDQ might not obey truly the 1-wire spec. + * So calculate CRC based on module parameter. + */ + cs = w1_calc_crc8((u8 *)&rn_le, 7); + id = (cs << 56) | module_id; + + slave_found(master_dev, id); +} + +static int _omap_hdq_reset(struct hdq_data *hdq_data) +{ + int ret; + u8 tmp_status; + + hdq_reg_out(hdq_data, OMAP_HDQ_SYSCONFIG, + OMAP_HDQ_SYSCONFIG_SOFTRESET); + /* + * Select HDQ/1W mode & enable clocks. + * It is observed that INT flags can't be cleared via a read and GO/INIT + * won't return to zero if interrupt is disabled. So we always enable + * interrupt. + */ + hdq_reg_out(hdq_data, OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE | + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK); + + /* wait for reset to complete */ + ret = hdq_wait_for_flag(hdq_data, OMAP_HDQ_SYSSTATUS, + OMAP_HDQ_SYSSTATUS_RESETDONE, OMAP_HDQ_FLAG_SET, &tmp_status); + if (ret) + dev_dbg(hdq_data->dev, "timeout waiting HDQ reset, %x", + tmp_status); + else { + hdq_reg_out(hdq_data, OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE | + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK | + hdq_data->mode); + hdq_reg_out(hdq_data, OMAP_HDQ_SYSCONFIG, + OMAP_HDQ_SYSCONFIG_AUTOIDLE); + } + + return ret; +} + +/* Issue break pulse to the device */ +static int omap_hdq_break(struct hdq_data *hdq_data) +{ + int ret = 0; + u8 tmp_status; + unsigned long irqflags; + + ret = mutex_lock_interruptible(&hdq_data->hdq_mutex); + if (ret < 0) { + dev_dbg(hdq_data->dev, "Could not acquire mutex\n"); + ret = -EINTR; + goto rtn; + } + + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags); + /* clear interrupt flags via a dummy read */ + hdq_reg_in(hdq_data, OMAP_HDQ_INT_STATUS); + /* ISR loads it with new INT_STATUS */ + hdq_data->hdq_irqstatus = 0; + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags); + + /* set the INIT and GO bit */ + hdq_reg_merge(hdq_data, OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_INITIALIZATION | OMAP_HDQ_CTRL_STATUS_GO, + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_INITIALIZATION | + OMAP_HDQ_CTRL_STATUS_GO); + + /* wait for the TIMEOUT bit */ + ret = wait_event_timeout(hdq_wait_queue, + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT); + if (ret == 0) { + dev_dbg(hdq_data->dev, "break wait elapsed\n"); + ret = -EINTR; + goto out; + } + + tmp_status = hdq_data->hdq_irqstatus; + /* check irqstatus */ + if (!(tmp_status & OMAP_HDQ_INT_STATUS_TIMEOUT)) { + dev_dbg(hdq_data->dev, "timeout waiting for TIMEOUT, %x\n", + tmp_status); + ret = -ETIMEDOUT; + goto out; + } + + /* + * check for the presence detect bit to get + * set to show that the slave is responding + */ + if (!(hdq_reg_in(hdq_data, OMAP_HDQ_CTRL_STATUS) & + OMAP_HDQ_CTRL_STATUS_PRESENCE)) { + dev_dbg(hdq_data->dev, "Presence bit not set\n"); + ret = -ETIMEDOUT; + goto out; + } + + /* + * wait for both INIT and GO bits rerurn to zero. + * zero wait time expected for interrupt mode. + */ + ret = hdq_wait_for_flag(hdq_data, OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_INITIALIZATION | + OMAP_HDQ_CTRL_STATUS_GO, OMAP_HDQ_FLAG_CLEAR, + &tmp_status); + if (ret) + dev_dbg(hdq_data->dev, "timeout waiting INIT&GO bits" + " return to zero, %x\n", tmp_status); + +out: + mutex_unlock(&hdq_data->hdq_mutex); +rtn: + return ret; +} + +static int hdq_read_byte(struct hdq_data *hdq_data, u8 *val) +{ + int ret = 0; + u8 status; + + ret = mutex_lock_interruptible(&hdq_data->hdq_mutex); + if (ret < 0) { + ret = -EINTR; + goto rtn; + } + + if (!hdq_data->hdq_usecount) { + ret = -EINVAL; + goto out; + } + + if (!(hdq_data->hdq_irqstatus & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) { + hdq_reg_merge(hdq_data, OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO, + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO); + /* + * The RX comes immediately after TX. + */ + wait_event_timeout(hdq_wait_queue, + (hdq_data->hdq_irqstatus + & OMAP_HDQ_INT_STATUS_RXCOMPLETE), + OMAP_HDQ_TIMEOUT); + + hdq_reg_merge(hdq_data, OMAP_HDQ_CTRL_STATUS, 0, + OMAP_HDQ_CTRL_STATUS_DIR); + status = hdq_data->hdq_irqstatus; + /* check irqstatus */ + if (!(status & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) { + dev_dbg(hdq_data->dev, "timeout waiting for" + " RXCOMPLETE, %x", status); + ret = -ETIMEDOUT; + goto out; + } + } + /* the data is ready. Read it in! */ + *val = hdq_reg_in(hdq_data, OMAP_HDQ_RX_DATA); +out: + mutex_unlock(&hdq_data->hdq_mutex); +rtn: + return ret; + +} + +/* Enable clocks and set the controller to HDQ/1W mode */ +static int omap_hdq_get(struct hdq_data *hdq_data) +{ + int ret = 0; + + ret = mutex_lock_interruptible(&hdq_data->hdq_mutex); + if (ret < 0) { + ret = -EINTR; + goto rtn; + } + + if (OMAP_HDQ_MAX_USER == hdq_data->hdq_usecount) { + dev_dbg(hdq_data->dev, "attempt to exceed the max use count"); + ret = -EINVAL; + goto out; + } else { + hdq_data->hdq_usecount++; + try_module_get(THIS_MODULE); + if (1 == hdq_data->hdq_usecount) { + + pm_runtime_get_sync(hdq_data->dev); + + /* make sure HDQ/1W is out of reset */ + if (!(hdq_reg_in(hdq_data, OMAP_HDQ_SYSSTATUS) & + OMAP_HDQ_SYSSTATUS_RESETDONE)) { + ret = _omap_hdq_reset(hdq_data); + if (ret) + /* back up the count */ + hdq_data->hdq_usecount--; + } else { + /* select HDQ/1W mode & enable clocks */ + hdq_reg_out(hdq_data, OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE | + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK | + hdq_data->mode); + hdq_reg_out(hdq_data, OMAP_HDQ_SYSCONFIG, + OMAP_HDQ_SYSCONFIG_NOIDLE); + hdq_reg_in(hdq_data, OMAP_HDQ_INT_STATUS); + } + } + } + +out: + mutex_unlock(&hdq_data->hdq_mutex); +rtn: + return ret; +} + +/* Disable clocks to the module */ +static int omap_hdq_put(struct hdq_data *hdq_data) +{ + int ret = 0; + + ret = mutex_lock_interruptible(&hdq_data->hdq_mutex); + if (ret < 0) + return -EINTR; + + hdq_reg_out(hdq_data, OMAP_HDQ_SYSCONFIG, + OMAP_HDQ_SYSCONFIG_AUTOIDLE); + if (0 == hdq_data->hdq_usecount) { + dev_dbg(hdq_data->dev, "attempt to decrement use count" + " when it is zero"); + ret = -EINVAL; + } else { + hdq_data->hdq_usecount--; + module_put(THIS_MODULE); + if (0 == hdq_data->hdq_usecount) + pm_runtime_put_sync(hdq_data->dev); + } + mutex_unlock(&hdq_data->hdq_mutex); + + return ret; +} + +/* + * W1 triplet callback function - used for searching ROM addresses. + * Registered only when controller is in 1-wire mode. + */ +static u8 omap_w1_triplet(void *_hdq, u8 bdir) +{ + u8 id_bit, comp_bit; + int err; + u8 ret = 0x3; /* no slaves responded */ + struct hdq_data *hdq_data = _hdq; + u8 ctrl = OMAP_HDQ_CTRL_STATUS_SINGLE | OMAP_HDQ_CTRL_STATUS_GO | + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK; + u8 mask = ctrl | OMAP_HDQ_CTRL_STATUS_DIR; + + omap_hdq_get(_hdq); + + err = mutex_lock_interruptible(&hdq_data->hdq_mutex); + if (err < 0) { + dev_dbg(hdq_data->dev, "Could not acquire mutex\n"); + goto rtn; + } + + hdq_data->hdq_irqstatus = 0; + /* read id_bit */ + hdq_reg_merge(_hdq, OMAP_HDQ_CTRL_STATUS, + ctrl | OMAP_HDQ_CTRL_STATUS_DIR, mask); + err = wait_event_timeout(hdq_wait_queue, + (hdq_data->hdq_irqstatus + & OMAP_HDQ_INT_STATUS_RXCOMPLETE), + OMAP_HDQ_TIMEOUT); + if (err == 0) { + dev_dbg(hdq_data->dev, "RX wait elapsed\n"); + goto out; + } + id_bit = (hdq_reg_in(_hdq, OMAP_HDQ_RX_DATA) & 0x01); + + hdq_data->hdq_irqstatus = 0; + /* read comp_bit */ + hdq_reg_merge(_hdq, OMAP_HDQ_CTRL_STATUS, + ctrl | OMAP_HDQ_CTRL_STATUS_DIR, mask); + err = wait_event_timeout(hdq_wait_queue, + (hdq_data->hdq_irqstatus + & OMAP_HDQ_INT_STATUS_RXCOMPLETE), + OMAP_HDQ_TIMEOUT); + if (err == 0) { + dev_dbg(hdq_data->dev, "RX wait elapsed\n"); + goto out; + } + comp_bit = (hdq_reg_in(_hdq, OMAP_HDQ_RX_DATA) & 0x01); + + if (id_bit && comp_bit) { + ret = 0x03; /* no slaves responded */ + goto out; + } + if (!id_bit && !comp_bit) { + /* Both bits are valid, take the direction given */ + ret = bdir ? 0x04 : 0; + } else { + /* Only one bit is valid, take that direction */ + bdir = id_bit; + ret = id_bit ? 0x05 : 0x02; + } + + /* write bdir bit */ + hdq_reg_out(_hdq, OMAP_HDQ_TX_DATA, bdir); + hdq_reg_merge(_hdq, OMAP_HDQ_CTRL_STATUS, ctrl, mask); + err = wait_event_timeout(hdq_wait_queue, + (hdq_data->hdq_irqstatus + & OMAP_HDQ_INT_STATUS_TXCOMPLETE), + OMAP_HDQ_TIMEOUT); + if (err == 0) { + dev_dbg(hdq_data->dev, "TX wait elapsed\n"); + goto out; + } + + hdq_reg_merge(_hdq, OMAP_HDQ_CTRL_STATUS, 0, + OMAP_HDQ_CTRL_STATUS_SINGLE); + +out: + mutex_unlock(&hdq_data->hdq_mutex); +rtn: + omap_hdq_put(_hdq); + return ret; +} + +/* reset callback */ +static u8 omap_w1_reset_bus(void *_hdq) +{ + omap_hdq_get(_hdq); + omap_hdq_break(_hdq); + omap_hdq_put(_hdq); + return 0; +} + +/* Read a byte of data from the device */ +static u8 omap_w1_read_byte(void *_hdq) +{ + struct hdq_data *hdq_data = _hdq; + u8 val = 0; + int ret; + + /* First write to initialize the transfer */ + if (hdq_data->init_trans == 0) + omap_hdq_get(hdq_data); + + ret = hdq_read_byte(hdq_data, &val); + if (ret) { + ret = mutex_lock_interruptible(&hdq_data->hdq_mutex); + if (ret < 0) { + dev_dbg(hdq_data->dev, "Could not acquire mutex\n"); + return -EINTR; + } + hdq_data->init_trans = 0; + mutex_unlock(&hdq_data->hdq_mutex); + omap_hdq_put(hdq_data); + return -1; + } + + hdq_disable_interrupt(hdq_data, OMAP_HDQ_CTRL_STATUS, + ~OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK); + + /* Write followed by a read, release the module */ + if (hdq_data->init_trans) { + ret = mutex_lock_interruptible(&hdq_data->hdq_mutex); + if (ret < 0) { + dev_dbg(hdq_data->dev, "Could not acquire mutex\n"); + return -EINTR; + } + hdq_data->init_trans = 0; + mutex_unlock(&hdq_data->hdq_mutex); + omap_hdq_put(hdq_data); + } + + return val; +} + +/* Write a byte of data to the device */ +static void omap_w1_write_byte(void *_hdq, u8 byte) +{ + struct hdq_data *hdq_data = _hdq; + int ret; + u8 status; + + /* First write to initialize the transfer */ + if (hdq_data->init_trans == 0) + omap_hdq_get(hdq_data); + + /* + * We need to reset the slave before + * issuing the SKIP ROM command, else + * the slave will not work. + */ + if (byte == W1_SKIP_ROM) + omap_hdq_break(hdq_data); + + ret = mutex_lock_interruptible(&hdq_data->hdq_mutex); + if (ret < 0) { + dev_dbg(hdq_data->dev, "Could not acquire mutex\n"); + return; + } + hdq_data->init_trans++; + mutex_unlock(&hdq_data->hdq_mutex); + + ret = hdq_write_byte(hdq_data, byte, &status); + if (ret < 0) { + dev_dbg(hdq_data->dev, "TX failure:Ctrl status %x\n", status); + return; + } + + /* Second write, data transferred. Release the module */ + if (hdq_data->init_trans > 1) { + omap_hdq_put(hdq_data); + ret = mutex_lock_interruptible(&hdq_data->hdq_mutex); + if (ret < 0) { + dev_dbg(hdq_data->dev, "Could not acquire mutex\n"); + return; + } + hdq_data->init_trans = 0; + mutex_unlock(&hdq_data->hdq_mutex); + } +} + +static struct w1_bus_master omap_w1_master = { + .read_byte = omap_w1_read_byte, + .write_byte = omap_w1_write_byte, + .reset_bus = omap_w1_reset_bus, +}; + +static int omap_hdq_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hdq_data *hdq_data; + struct resource *res; + int ret, irq; + u8 rev; + const char *mode; + + hdq_data = devm_kzalloc(dev, sizeof(*hdq_data), GFP_KERNEL); + if (!hdq_data) { + dev_dbg(&pdev->dev, "unable to allocate memory\n"); + return -ENOMEM; + } + + hdq_data->dev = dev; + platform_set_drvdata(pdev, hdq_data); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdq_data->hdq_base = devm_ioremap_resource(dev, res); + if (IS_ERR(hdq_data->hdq_base)) + return PTR_ERR(hdq_data->hdq_base); + + hdq_data->hdq_usecount = 0; + hdq_data->rrw = 0; + mutex_init(&hdq_data->hdq_mutex); + + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) { + dev_dbg(&pdev->dev, "pm_runtime_get_sync failed\n"); + goto err_w1; + } + + ret = _omap_hdq_reset(hdq_data); + if (ret) { + dev_dbg(&pdev->dev, "reset failed\n"); + goto err_irq; + } + + rev = hdq_reg_in(hdq_data, OMAP_HDQ_REVISION); + dev_info(&pdev->dev, "OMAP HDQ Hardware Rev %c.%c. Driver in %s mode\n", + (rev >> 4) + '0', (rev & 0x0f) + '0', "Interrupt"); + + spin_lock_init(&hdq_data->hdq_spinlock); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_dbg(&pdev->dev, "Failed to get IRQ: %d\n", irq); + ret = irq; + goto err_irq; + } + + ret = devm_request_irq(dev, irq, hdq_isr, 0, "omap_hdq", hdq_data); + if (ret < 0) { + dev_dbg(&pdev->dev, "could not request irq\n"); + goto err_irq; + } + + omap_hdq_break(hdq_data); + + pm_runtime_put_sync(&pdev->dev); + + ret = of_property_read_string(pdev->dev.of_node, "ti,mode", &mode); + if (ret < 0 || !strcmp(mode, "hdq")) { + hdq_data->mode = 0; + omap_w1_master.search = omap_w1_search_bus; + } else { + hdq_data->mode = 1; + omap_w1_master.triplet = omap_w1_triplet; + } + + omap_w1_master.data = hdq_data; + + ret = w1_add_master_device(&omap_w1_master); + if (ret) { + dev_dbg(&pdev->dev, "Failure in registering w1 master\n"); + goto err_w1; + } + + return 0; + +err_irq: + pm_runtime_put_sync(&pdev->dev); +err_w1: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int omap_hdq_remove(struct platform_device *pdev) +{ + struct hdq_data *hdq_data = platform_get_drvdata(pdev); + + mutex_lock(&hdq_data->hdq_mutex); + + if (hdq_data->hdq_usecount) { + dev_dbg(&pdev->dev, "removed when use count is not zero\n"); + mutex_unlock(&hdq_data->hdq_mutex); + return -EBUSY; + } + + mutex_unlock(&hdq_data->hdq_mutex); + + /* remove module dependency */ + pm_runtime_disable(&pdev->dev); + + w1_remove_master_device(&omap_w1_master); + + return 0; +} + +static const struct of_device_id omap_hdq_dt_ids[] = { + { .compatible = "ti,omap3-1w" }, + { .compatible = "ti,am4372-hdq" }, + {} +}; +MODULE_DEVICE_TABLE(of, omap_hdq_dt_ids); + +static struct platform_driver omap_hdq_driver = { + .probe = omap_hdq_probe, + .remove = omap_hdq_remove, + .driver = { + .name = "omap_hdq", + .of_match_table = omap_hdq_dt_ids, + }, +}; +module_platform_driver(omap_hdq_driver); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("HDQ-1W driver Library"); +MODULE_LICENSE("GPL"); diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c new file mode 100644 index 000000000..55e11bf8e --- /dev/null +++ b/drivers/w1/masters/w1-gpio.c @@ -0,0 +1,209 @@ +/* + * w1-gpio - GPIO w1 bus master driver + * + * Copyright (C) 2007 Ville Syrjala <syrjala@sci.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/w1-gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/of_platform.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/delay.h> + +#include <linux/w1.h> + +static u8 w1_gpio_set_pullup(void *data, int delay) +{ + struct w1_gpio_platform_data *pdata = data; + + if (delay) { + pdata->pullup_duration = delay; + } else { + if (pdata->pullup_duration) { + /* + * This will OVERRIDE open drain emulation and force-pull + * the line high for some time. + */ + gpiod_set_raw_value(pdata->gpiod, 1); + msleep(pdata->pullup_duration); + /* + * This will simply set the line as input since we are doing + * open drain emulation in the GPIO library. + */ + gpiod_set_value(pdata->gpiod, 1); + } + pdata->pullup_duration = 0; + } + + return 0; +} + +static void w1_gpio_write_bit(void *data, u8 bit) +{ + struct w1_gpio_platform_data *pdata = data; + + gpiod_set_value(pdata->gpiod, bit); +} + +static u8 w1_gpio_read_bit(void *data) +{ + struct w1_gpio_platform_data *pdata = data; + + return gpiod_get_value(pdata->gpiod) ? 1 : 0; +} + +#if defined(CONFIG_OF) +static const struct of_device_id w1_gpio_dt_ids[] = { + { .compatible = "w1-gpio" }, + {} +}; +MODULE_DEVICE_TABLE(of, w1_gpio_dt_ids); +#endif + +static int w1_gpio_probe(struct platform_device *pdev) +{ + struct w1_bus_master *master; + struct w1_gpio_platform_data *pdata; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + /* Enforce open drain mode by default */ + enum gpiod_flags gflags = GPIOD_OUT_LOW_OPEN_DRAIN; + int err; + + if (of_have_populated_dt()) { + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + /* + * This parameter means that something else than the gpiolib has + * already set the line into open drain mode, so we should just + * driver it high/low like we are in full control of the line and + * open drain will happen transparently. + */ + if (of_get_property(np, "linux,open-drain", NULL)) + gflags = GPIOD_OUT_LOW; + + pdev->dev.platform_data = pdata; + } + pdata = dev_get_platdata(dev); + + if (!pdata) { + dev_err(dev, "No configuration data\n"); + return -ENXIO; + } + + master = devm_kzalloc(dev, sizeof(struct w1_bus_master), + GFP_KERNEL); + if (!master) { + dev_err(dev, "Out of memory\n"); + return -ENOMEM; + } + + pdata->gpiod = devm_gpiod_get_index(dev, NULL, 0, gflags); + if (IS_ERR(pdata->gpiod)) { + dev_err(dev, "gpio_request (pin) failed\n"); + return PTR_ERR(pdata->gpiod); + } + + pdata->pullup_gpiod = + devm_gpiod_get_index_optional(dev, NULL, 1, GPIOD_OUT_LOW); + if (IS_ERR(pdata->pullup_gpiod)) { + dev_err(dev, "gpio_request_one " + "(ext_pullup_enable_pin) failed\n"); + return PTR_ERR(pdata->pullup_gpiod); + } + + master->data = pdata; + master->read_bit = w1_gpio_read_bit; + gpiod_direction_output(pdata->gpiod, 1); + master->write_bit = w1_gpio_write_bit; + + /* + * If we are using open drain emulation from the GPIO library, + * we need to use this pullup function that hammers the line + * high using a raw accessor to provide pull-up for the w1 + * line. + */ + if (gflags == GPIOD_OUT_LOW_OPEN_DRAIN) + master->set_pullup = w1_gpio_set_pullup; + + err = w1_add_master_device(master); + if (err) { + dev_err(dev, "w1_add_master device failed\n"); + return err; + } + + if (pdata->enable_external_pullup) + pdata->enable_external_pullup(1); + + if (pdata->pullup_gpiod) + gpiod_set_value(pdata->pullup_gpiod, 1); + + platform_set_drvdata(pdev, master); + + return 0; +} + +static int w1_gpio_remove(struct platform_device *pdev) +{ + struct w1_bus_master *master = platform_get_drvdata(pdev); + struct w1_gpio_platform_data *pdata = dev_get_platdata(&pdev->dev); + + if (pdata->enable_external_pullup) + pdata->enable_external_pullup(0); + + if (pdata->pullup_gpiod) + gpiod_set_value(pdata->pullup_gpiod, 0); + + w1_remove_master_device(master); + + return 0; +} + +static int __maybe_unused w1_gpio_suspend(struct device *dev) +{ + struct w1_gpio_platform_data *pdata = dev_get_platdata(dev); + + if (pdata->enable_external_pullup) + pdata->enable_external_pullup(0); + + return 0; +} + +static int __maybe_unused w1_gpio_resume(struct device *dev) +{ + struct w1_gpio_platform_data *pdata = dev_get_platdata(dev); + + if (pdata->enable_external_pullup) + pdata->enable_external_pullup(1); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(w1_gpio_pm_ops, w1_gpio_suspend, w1_gpio_resume); + +static struct platform_driver w1_gpio_driver = { + .driver = { + .name = "w1-gpio", + .pm = &w1_gpio_pm_ops, + .of_match_table = of_match_ptr(w1_gpio_dt_ids), + }, + .probe = w1_gpio_probe, + .remove = w1_gpio_remove, +}; + +module_platform_driver(w1_gpio_driver); + +MODULE_DESCRIPTION("GPIO w1 bus master driver"); +MODULE_AUTHOR("Ville Syrjala <syrjala@sci.fi>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig new file mode 100644 index 000000000..e22fdedda --- /dev/null +++ b/drivers/w1/slaves/Kconfig @@ -0,0 +1,154 @@ +# +# 1-wire slaves configuration +# + +menu "1-wire Slaves" + +config W1_SLAVE_THERM + tristate "Thermal family implementation" + help + Say Y here if you want to connect 1-wire thermal sensors to your + wire. + +config W1_SLAVE_SMEM + tristate "Simple 64bit memory family implementation" + help + Say Y here if you want to connect 1-wire + simple 64bit memory rom(ds2401/ds2411/ds1990*) to your wire. + +config W1_SLAVE_DS2405 + tristate "DS2405 Addressable Switch" + help + Say Y or M here if you want to use a DS2405 1-wire + single-channel addressable switch. + This device can also work as a single-channel + binary remote sensor. + +config W1_SLAVE_DS2408 + tristate "8-Channel Addressable Switch (IO Expander) 0x29 family support (DS2408)" + help + Say Y here if you want to use a 1-wire + DS2408 8-Channel Addressable Switch device support + +config W1_SLAVE_DS2408_READBACK + bool "Read-back values written to DS2408's output register" + depends on W1_SLAVE_DS2408 + default y + help + Enabling this will cause the driver to read back the values written + to the chip's output register in order to detect errors. + + This is slower but useful when debugging chips and/or busses. + +config W1_SLAVE_DS2413 + tristate "Dual Channel Addressable Switch 0x3a family support (DS2413)" + help + Say Y here if you want to use a 1-wire + DS2413 Dual Channel Addressable Switch device support + +config W1_SLAVE_DS2406 + tristate "Dual Channel Addressable Switch 0x12 family support (DS2406)" + select CRC16 + help + Say Y or M here if you want to use a 1-wire + DS2406 Dual Channel Addressable Switch. EPROM read/write + support for these devices is not implemented. + +config W1_SLAVE_DS2423 + tristate "Counter 1-wire device (DS2423)" + select CRC16 + help + If you enable this you can read the counter values available + in the DS2423 chipset from the w1_slave file under the + sys file system. + + Say Y here if you want to use a 1-wire + counter family device (DS2423). + +config W1_SLAVE_DS2805 + tristate "112-byte EEPROM support (DS28E05)" + help + Say Y here if you want to use a 1-wire + is a 112-byte user-programmable EEPROM is + organized as 7 pages of 16 bytes each with 64bit + unique number. Requires OverDrive Speed to talk to. + +config W1_SLAVE_DS2431 + tristate "1kb EEPROM family support (DS2431)" + help + Say Y here if you want to use a 1-wire + 1kb EEPROM family device (DS2431) + +config W1_SLAVE_DS2433 + tristate "4kb EEPROM family support (DS2433)" + help + Say Y here if you want to use a 1-wire + 4kb EEPROM family device (DS2433). + +config W1_SLAVE_DS2433_CRC + bool "Protect DS2433 data with a CRC16" + depends on W1_SLAVE_DS2433 + select CRC16 + help + Say Y here to protect DS2433 data with a CRC16. + Each block has 30 bytes of data and a two byte CRC16. + Full block writes are only allowed if the CRC is valid. + +config W1_SLAVE_DS2438 + tristate "DS2438 Smart Battery Monitor 0x26 family support" + help + Say Y here if you want to use a 1-wire + DS2438 Smart Battery Monitor device support + +config W1_SLAVE_DS2780 + tristate "Dallas 2780 battery monitor chip" + help + If you enable this you will have the DS2780 battery monitor + chip support. + + The battery monitor chip is used in many batteries/devices + as the one who is responsible for charging/discharging/monitoring + Li+ batteries. + + If you are unsure, say N. + +config W1_SLAVE_DS2781 + tristate "Dallas 2781 battery monitor chip" + help + If you enable this you will have the DS2781 battery monitor + chip support. + + The battery monitor chip is used in many batteries/devices + as the one who is responsible for charging/discharging/monitoring + Li+ batteries. + + If you are unsure, say N. + +config W1_SLAVE_DS28E04 + tristate "4096-Bit Addressable 1-Wire EEPROM with PIO (DS28E04-100)" + select CRC16 + help + If you enable this you will have the DS28E04-100 + chip support. + + Say Y here if you want to use a 1-wire + 4kb EEPROM with PIO family device (DS28E04). + + If you are unsure, say N. + +config W1_SLAVE_DS28E17 + tristate "1-wire-to-I2C master bridge (DS28E17)" + select CRC16 + depends on I2C + help + Say Y here if you want to use the DS28E17 1-wire-to-I2C master bridge. + For each DS28E17 detected, a new I2C adapter is created within the + kernel. I2C devices on that bus can be configured to be used by the + kernel and userspace tools as on any other "native" I2C bus. + + This driver is also available as a module. If so, the module + will be called w1_ds28e17. + + If you are unsure, say N. + +endmenu diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile new file mode 100644 index 000000000..eab29f151 --- /dev/null +++ b/drivers/w1/slaves/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the Dallas's 1-wire slaves. +# + +obj-$(CONFIG_W1_SLAVE_THERM) += w1_therm.o +obj-$(CONFIG_W1_SLAVE_SMEM) += w1_smem.o +obj-$(CONFIG_W1_SLAVE_DS2405) += w1_ds2405.o +obj-$(CONFIG_W1_SLAVE_DS2408) += w1_ds2408.o +obj-$(CONFIG_W1_SLAVE_DS2413) += w1_ds2413.o +obj-$(CONFIG_W1_SLAVE_DS2406) += w1_ds2406.o +obj-$(CONFIG_W1_SLAVE_DS2423) += w1_ds2423.o +obj-$(CONFIG_W1_SLAVE_DS2431) += w1_ds2431.o +obj-$(CONFIG_W1_SLAVE_DS2805) += w1_ds2805.o +obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o +obj-$(CONFIG_W1_SLAVE_DS2438) += w1_ds2438.o +obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o +obj-$(CONFIG_W1_SLAVE_DS2781) += w1_ds2781.o +obj-$(CONFIG_W1_SLAVE_DS28E04) += w1_ds28e04.o +obj-$(CONFIG_W1_SLAVE_DS28E17) += w1_ds28e17.o diff --git a/drivers/w1/slaves/w1_ds2405.c b/drivers/w1/slaves/w1_ds2405.c new file mode 100644 index 000000000..42a1e8106 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2405.c @@ -0,0 +1,228 @@ +/* + * w1_ds2405.c + * + * Copyright (c) 2017 Maciej S. Szmigiero <mail@maciej.szmigiero.name> + * Based on w1_therm.c copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the therms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mutex.h> +#include <linux/string.h> +#include <linux/types.h> + +#include <linux/w1.h> + +#define W1_FAMILY_DS2405 0x05 + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Maciej S. Szmigiero <mail@maciej.szmigiero.name>"); +MODULE_DESCRIPTION("Driver for 1-wire Dallas DS2405 PIO."); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2405)); + +static int w1_ds2405_select(struct w1_slave *sl, bool only_active) +{ + struct w1_master *dev = sl->master; + + u64 dev_addr = le64_to_cpu(*(u64 *)&sl->reg_num); + unsigned int bit_ctr; + + if (w1_reset_bus(dev) != 0) + return 0; + + /* + * We cannot use a normal Match ROM command + * since doing so would toggle PIO state + */ + w1_write_8(dev, only_active ? W1_ALARM_SEARCH : W1_SEARCH); + + for (bit_ctr = 0; bit_ctr < 64; bit_ctr++) { + int bit2send = !!(dev_addr & BIT(bit_ctr)); + u8 ret; + + ret = w1_triplet(dev, bit2send); + + if ((ret & (BIT(0) | BIT(1))) == + (BIT(0) | BIT(1))) /* no devices found */ + return 0; + + if (!!(ret & BIT(2)) != bit2send) + /* wrong direction taken - no such device */ + return 0; + } + + return 1; +} + +static int w1_ds2405_read_pio(struct w1_slave *sl) +{ + if (w1_ds2405_select(sl, true)) + return 0; /* "active" means PIO is low */ + + if (w1_ds2405_select(sl, false)) + return 1; + + return -ENODEV; +} + +static ssize_t state_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct w1_master *dev = sl->master; + + int ret; + ssize_t f_retval; + u8 state; + + ret = mutex_lock_interruptible(&dev->bus_mutex); + if (ret) + return ret; + + if (!w1_ds2405_select(sl, false)) { + f_retval = -ENODEV; + goto out_unlock; + } + + state = w1_read_8(dev); + if (state != 0 && + state != 0xff) { + dev_err(device, "non-consistent state %x\n", state); + f_retval = -EIO; + goto out_unlock; + } + + *buf = state ? '1' : '0'; + f_retval = 1; + +out_unlock: + w1_reset_bus(dev); + mutex_unlock(&dev->bus_mutex); + + return f_retval; +} + +static ssize_t output_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct w1_master *dev = sl->master; + + int ret; + ssize_t f_retval; + + ret = mutex_lock_interruptible(&dev->bus_mutex); + if (ret) + return ret; + + ret = w1_ds2405_read_pio(sl); + if (ret < 0) { + f_retval = ret; + goto out_unlock; + } + + *buf = ret ? '1' : '0'; + f_retval = 1; + +out_unlock: + w1_reset_bus(dev); + mutex_unlock(&dev->bus_mutex); + + return f_retval; +} + +static ssize_t output_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct w1_master *dev = sl->master; + + int ret, current_pio; + unsigned int val; + ssize_t f_retval; + + if (count < 1) + return -EINVAL; + + if (sscanf(buf, " %u%n", &val, &ret) < 1) + return -EINVAL; + + if (val != 0 && val != 1) + return -EINVAL; + + f_retval = ret; + + ret = mutex_lock_interruptible(&dev->bus_mutex); + if (ret) + return ret; + + current_pio = w1_ds2405_read_pio(sl); + if (current_pio < 0) { + f_retval = current_pio; + goto out_unlock; + } + + if (current_pio == val) + goto out_unlock; + + if (w1_reset_bus(dev) != 0) { + f_retval = -ENODEV; + goto out_unlock; + } + + /* + * can't use w1_reset_select_slave() here since it uses Skip ROM if + * there is only one device on bus + */ + do { + u64 dev_addr = le64_to_cpu(*(u64 *)&sl->reg_num); + u8 cmd[9]; + + cmd[0] = W1_MATCH_ROM; + memcpy(&cmd[1], &dev_addr, sizeof(dev_addr)); + + w1_write_block(dev, cmd, sizeof(cmd)); + } while (0); + +out_unlock: + w1_reset_bus(dev); + mutex_unlock(&dev->bus_mutex); + + return f_retval; +} + +static DEVICE_ATTR_RO(state); +static DEVICE_ATTR_RW(output); + +static struct attribute *w1_ds2405_attrs[] = { + &dev_attr_state.attr, + &dev_attr_output.attr, + NULL +}; + +ATTRIBUTE_GROUPS(w1_ds2405); + +static struct w1_family_ops w1_ds2405_fops = { + .groups = w1_ds2405_groups +}; + +static struct w1_family w1_family_ds2405 = { + .fid = W1_FAMILY_DS2405, + .fops = &w1_ds2405_fops +}; + +module_w1_family(w1_family_ds2405); diff --git a/drivers/w1/slaves/w1_ds2406.c b/drivers/w1/slaves/w1_ds2406.c new file mode 100644 index 000000000..fac266366 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2406.c @@ -0,0 +1,156 @@ +/* + * w1_ds2406.c - w1 family 12 (DS2406) driver + * based on w1_ds2413.c by Mariusz Bialonczyk <manio@skyboo.net> + * + * Copyright (c) 2014 Scott Alfter <scott@alfter.us> + * + * 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 <linux/crc16.h> + +#include <linux/w1.h> + +#define W1_FAMILY_DS2406 0x12 + +#define W1_F12_FUNC_READ_STATUS 0xAA +#define W1_F12_FUNC_WRITE_STATUS 0x55 + +static ssize_t w1_f12_read_state( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + u8 w1_buf[6]={W1_F12_FUNC_READ_STATUS, 7, 0, 0, 0, 0}; + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u16 crc=0; + int i; + ssize_t rtnval=1; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->bus_mutex); + return -EIO; + } + + w1_write_block(sl->master, w1_buf, 3); + w1_read_block(sl->master, w1_buf+3, 3); + for (i=0; i<6; i++) + crc=crc16_byte(crc, w1_buf[i]); + if (crc==0xb001) /* good read? */ + *buf=((w1_buf[3]>>5)&3)|0x30; + else + rtnval=-EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return rtnval; +} + +static ssize_t w1_f12_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[6]={W1_F12_FUNC_WRITE_STATUS, 7, 0, 0, 0, 0}; + u16 crc=0; + int i; + ssize_t rtnval=1; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->bus_mutex); + return -EIO; + } + + w1_buf[3] = (((*buf)&3)<<5)|0x1F; + w1_write_block(sl->master, w1_buf, 4); + w1_read_block(sl->master, w1_buf+4, 2); + for (i=0; i<6; i++) + crc=crc16_byte(crc, w1_buf[i]); + if (crc==0xb001) /* good read? */ + w1_write_8(sl->master, 0xFF); + else + rtnval=-EIO; + + mutex_unlock(&sl->master->bus_mutex); + return rtnval; +} + +#define NB_SYSFS_BIN_FILES 2 +static struct bin_attribute w1_f12_sysfs_bin_files[NB_SYSFS_BIN_FILES] = { + { + .attr = { + .name = "state", + .mode = S_IRUGO, + }, + .size = 1, + .read = w1_f12_read_state, + }, + { + .attr = { + .name = "output", + .mode = S_IRUGO | S_IWUSR | S_IWGRP, + }, + .size = 1, + .write = w1_f12_write_output, + } +}; + +static int w1_f12_add_slave(struct w1_slave *sl) +{ + int err = 0; + int i; + + for (i = 0; i < NB_SYSFS_BIN_FILES && !err; ++i) + err = sysfs_create_bin_file( + &sl->dev.kobj, + &(w1_f12_sysfs_bin_files[i])); + if (err) + while (--i >= 0) + sysfs_remove_bin_file(&sl->dev.kobj, + &(w1_f12_sysfs_bin_files[i])); + return err; +} + +static void w1_f12_remove_slave(struct w1_slave *sl) +{ + int i; + for (i = NB_SYSFS_BIN_FILES - 1; i >= 0; --i) + sysfs_remove_bin_file(&sl->dev.kobj, + &(w1_f12_sysfs_bin_files[i])); +} + +static struct w1_family_ops w1_f12_fops = { + .add_slave = w1_f12_add_slave, + .remove_slave = w1_f12_remove_slave, +}; + +static struct w1_family w1_family_12 = { + .fid = W1_FAMILY_DS2406, + .fops = &w1_f12_fops, +}; +module_w1_family(w1_family_12); + +MODULE_AUTHOR("Scott Alfter <scott@alfter.us>"); +MODULE_DESCRIPTION("w1 family 12 driver for DS2406 2 Pin IO"); +MODULE_LICENSE("GPL"); diff --git a/drivers/w1/slaves/w1_ds2408.c b/drivers/w1/slaves/w1_ds2408.c new file mode 100644 index 000000000..b535d5ec3 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2408.c @@ -0,0 +1,353 @@ +/* + * 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 <linux/w1.h> + +#define W1_FAMILY_DS2408 0x29 + +#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 state_read(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 output_read(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 activity_read(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 cond_search_mask_read(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 cond_search_polarity_read(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 status_control_read(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 output_write(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 activity_write(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 status_control_write(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 BIN_ATTR_RO(state, 1); +static BIN_ATTR_RW(output, 1); +static BIN_ATTR_RW(activity, 1); +static BIN_ATTR_RO(cond_search_mask, 1); +static BIN_ATTR_RO(cond_search_polarity, 1); +static BIN_ATTR_RW(status_control, 1); + +static struct bin_attribute *w1_f29_bin_attrs[] = { + &bin_attr_state, + &bin_attr_output, + &bin_attr_activity, + &bin_attr_cond_search_mask, + &bin_attr_cond_search_polarity, + &bin_attr_status_control, + NULL, +}; + +static const struct attribute_group w1_f29_group = { + .bin_attrs = w1_f29_bin_attrs, +}; + +static const struct attribute_group *w1_f29_groups[] = { + &w1_f29_group, + NULL, +}; + +static struct w1_family_ops w1_f29_fops = { + .add_slave = w1_f29_disable_test_mode, + .groups = w1_f29_groups, +}; + +static struct w1_family w1_family_29 = { + .fid = W1_FAMILY_DS2408, + .fops = &w1_f29_fops, +}; +module_w1_family(w1_family_29); + +MODULE_AUTHOR("Jean-Francois Dagenais <dagenaisj@sonatest.com>"); +MODULE_DESCRIPTION("w1 family 29 driver for DS2408 8 Pin IO"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2408)); diff --git a/drivers/w1/slaves/w1_ds2413.c b/drivers/w1/slaves/w1_ds2413.c new file mode 100644 index 000000000..492e3d010 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2413.c @@ -0,0 +1,138 @@ +/* + * w1_ds2413.c - w1 family 3a (DS2413) driver + * based on w1_ds2408.c by Jean-Francois Dagenais <dagenaisj@sonatest.com> + * + * Copyright (c) 2013 Mariusz Bialonczyk <manio@skyboo.net> + * + * 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 <linux/w1.h> + +#define W1_FAMILY_DS2413 0x3A + +#define W1_F3A_RETRIES 3 +#define W1_F3A_FUNC_PIO_ACCESS_READ 0xF5 +#define W1_F3A_FUNC_PIO_ACCESS_WRITE 0x5A +#define W1_F3A_SUCCESS_CONFIRM_BYTE 0xAA + +static ssize_t state_read(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); + dev_dbg(&sl->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + + if (off != 0) + return 0; + 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; + } + + w1_write_8(sl->master, W1_F3A_FUNC_PIO_ACCESS_READ); + *buf = w1_read_8(sl->master); + + mutex_unlock(&sl->master->bus_mutex); + dev_dbg(&sl->dev, "mutex unlocked"); + + /* check for correct complement */ + if ((*buf & 0x0F) != ((~*buf >> 4) & 0x0F)) + return -EIO; + else + return 1; +} + +static BIN_ATTR_RO(state, 1); + +static ssize_t output_write(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]; + unsigned int retries = W1_F3A_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; + + /* according to the DS2413 datasheet the most significant 6 bits + should be set to "1"s, so do it now */ + *buf = *buf | 0xFC; + + while (retries--) { + w1_buf[0] = W1_F3A_FUNC_PIO_ACCESS_WRITE; + w1_buf[1] = *buf; + w1_buf[2] = ~(*buf); + w1_write_block(sl->master, w1_buf, 3); + + if (w1_read_8(sl->master) == W1_F3A_SUCCESS_CONFIRM_BYTE) { + mutex_unlock(&sl->master->bus_mutex); + dev_dbg(&sl->dev, "mutex unlocked, retries:%d", retries); + return 1; + } + if (w1_reset_resume_command(sl->master)) + goto error; + } + +error: + mutex_unlock(&sl->master->bus_mutex); + dev_dbg(&sl->dev, "mutex unlocked in error, retries:%d", retries); + return -EIO; +} + +static BIN_ATTR(output, S_IRUGO | S_IWUSR | S_IWGRP, NULL, output_write, 1); + +static struct bin_attribute *w1_f3a_bin_attrs[] = { + &bin_attr_state, + &bin_attr_output, + NULL, +}; + +static const struct attribute_group w1_f3a_group = { + .bin_attrs = w1_f3a_bin_attrs, +}; + +static const struct attribute_group *w1_f3a_groups[] = { + &w1_f3a_group, + NULL, +}; + +static struct w1_family_ops w1_f3a_fops = { + .groups = w1_f3a_groups, +}; + +static struct w1_family w1_family_3a = { + .fid = W1_FAMILY_DS2413, + .fops = &w1_f3a_fops, +}; +module_w1_family(w1_family_3a); + +MODULE_AUTHOR("Mariusz Bialonczyk <manio@skyboo.net>"); +MODULE_DESCRIPTION("w1 family 3a driver for DS2413 2 Pin IO"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2413)); diff --git a/drivers/w1/slaves/w1_ds2423.c b/drivers/w1/slaves/w1_ds2423.c new file mode 100644 index 000000000..050407c53 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2423.c @@ -0,0 +1,146 @@ +/* + * w1_ds2423.c + * + * Copyright (c) 2010 Mika Laitio <lamikr@pilppa.org> + * + * This driver will read and write the value of 4 counters to w1_slave file in + * sys filesystem. + * Inspired by the w1_therm and w1_ds2431 drivers. + * + * This program is free software; you can redistribute it and/or modify + * it under the therms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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/crc16.h> + +#include <linux/w1.h> + +#define W1_COUNTER_DS2423 0x1D + +#define CRC16_VALID 0xb001 +#define CRC16_INIT 0 + +#define COUNTER_COUNT 4 +#define READ_BYTE_COUNT 42 + +static ssize_t w1_slave_show(struct device *device, + struct device_attribute *attr, char *out_buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct w1_master *dev = sl->master; + u8 rbuf[COUNTER_COUNT * READ_BYTE_COUNT]; + u8 wrbuf[3]; + int rom_addr; + int read_byte_count; + int result; + ssize_t c; + int ii; + int p; + int crc; + + c = PAGE_SIZE; + rom_addr = (12 << 5) + 31; + wrbuf[0] = 0xA5; + wrbuf[1] = rom_addr & 0xFF; + wrbuf[2] = rom_addr >> 8; + mutex_lock(&dev->bus_mutex); + if (!w1_reset_select_slave(sl)) { + w1_write_block(dev, wrbuf, 3); + read_byte_count = 0; + for (p = 0; p < 4; p++) { + /* + * 1 byte for first bytes in ram page read + * 4 bytes for counter + * 4 bytes for zero bits + * 2 bytes for crc + * 31 remaining bytes from the ram page + */ + read_byte_count += w1_read_block(dev, + rbuf + (p * READ_BYTE_COUNT), READ_BYTE_COUNT); + for (ii = 0; ii < READ_BYTE_COUNT; ++ii) + c -= snprintf(out_buf + PAGE_SIZE - c, + c, "%02x ", + rbuf[(p * READ_BYTE_COUNT) + ii]); + if (read_byte_count != (p + 1) * READ_BYTE_COUNT) { + dev_warn(device, + "w1_counter_read() returned %u bytes " + "instead of %d bytes wanted.\n", + read_byte_count, + READ_BYTE_COUNT); + c -= snprintf(out_buf + PAGE_SIZE - c, + c, "crc=NO\n"); + } else { + if (p == 0) { + crc = crc16(CRC16_INIT, wrbuf, 3); + crc = crc16(crc, rbuf, 11); + } else { + /* + * DS2423 calculates crc from all bytes + * read after the previous crc bytes. + */ + crc = crc16(CRC16_INIT, + (rbuf + 11) + + ((p - 1) * READ_BYTE_COUNT), + READ_BYTE_COUNT); + } + if (crc == CRC16_VALID) { + result = 0; + for (ii = 4; ii > 0; ii--) { + result <<= 8; + result |= rbuf[(p * + READ_BYTE_COUNT) + ii]; + } + c -= snprintf(out_buf + PAGE_SIZE - c, + c, "crc=YES c=%d\n", result); + } else { + c -= snprintf(out_buf + PAGE_SIZE - c, + c, "crc=NO\n"); + } + } + } + } else { + c -= snprintf(out_buf + PAGE_SIZE - c, c, "Connection error"); + } + mutex_unlock(&dev->bus_mutex); + return PAGE_SIZE - c; +} + +static DEVICE_ATTR_RO(w1_slave); + +static struct attribute *w1_f1d_attrs[] = { + &dev_attr_w1_slave.attr, + NULL, +}; +ATTRIBUTE_GROUPS(w1_f1d); + +static struct w1_family_ops w1_f1d_fops = { + .groups = w1_f1d_groups, +}; + +static struct w1_family w1_family_1d = { + .fid = W1_COUNTER_DS2423, + .fops = &w1_f1d_fops, +}; +module_w1_family(w1_family_1d); + +MODULE_AUTHOR("Mika Laitio <lamikr@pilppa.org>"); +MODULE_DESCRIPTION("w1 family 1d driver for DS2423, 4 counters and 4kb ram"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_COUNTER_DS2423)); diff --git a/drivers/w1/slaves/w1_ds2431.c b/drivers/w1/slaves/w1_ds2431.c new file mode 100644 index 000000000..5adecd3fa --- /dev/null +++ b/drivers/w1/slaves/w1_ds2431.c @@ -0,0 +1,296 @@ +/* + * w1_ds2431.c - w1 family 2d (DS2431) driver + * + * Copyright (c) 2008 Bernhard Weirich <bernhard.weirich@riedel.net> + * + * Heavily inspired by w1_DS2433 driver from Ben Gardner <bgardner@wabtec.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/w1.h> + +#define W1_EEPROM_DS2431 0x2D + +#define W1_F2D_EEPROM_SIZE 128 +#define W1_F2D_PAGE_COUNT 4 +#define W1_F2D_PAGE_BITS 5 +#define W1_F2D_PAGE_SIZE (1<<W1_F2D_PAGE_BITS) +#define W1_F2D_PAGE_MASK 0x1F + +#define W1_F2D_SCRATCH_BITS 3 +#define W1_F2D_SCRATCH_SIZE (1<<W1_F2D_SCRATCH_BITS) +#define W1_F2D_SCRATCH_MASK (W1_F2D_SCRATCH_SIZE-1) + +#define W1_F2D_READ_EEPROM 0xF0 +#define W1_F2D_WRITE_SCRATCH 0x0F +#define W1_F2D_READ_SCRATCH 0xAA +#define W1_F2D_COPY_SCRATCH 0x55 + + +#define W1_F2D_TPROG_MS 11 + +#define W1_F2D_READ_RETRIES 10 +#define W1_F2D_READ_MAXLEN 8 + +/* + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f2d_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return size - off; + + return count; +} + +/* + * Read a block from W1 ROM two times and compares the results. + * If they are equal they are returned, otherwise the read + * is repeated W1_F2D_READ_RETRIES times. + * + * count must not exceed W1_F2D_READ_MAXLEN. + */ +static int w1_f2d_readblock(struct w1_slave *sl, int off, int count, char *buf) +{ + u8 wrbuf[3]; + u8 cmp[W1_F2D_READ_MAXLEN]; + int tries = W1_F2D_READ_RETRIES; + + do { + wrbuf[0] = W1_F2D_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, buf, count); + + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, cmp, count); + + if (!memcmp(cmp, buf, count)) + return 0; + } while (--tries); + + dev_err(&sl->dev, "proof reading failed %d times\n", + W1_F2D_READ_RETRIES); + + return -1; +} + +static ssize_t eeprom_read(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); + int todo = count; + + count = w1_f2d_fix_count(off, count, W1_F2D_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->bus_mutex); + + /* read directly from the EEPROM in chunks of W1_F2D_READ_MAXLEN */ + while (todo > 0) { + int block_read; + + if (todo >= W1_F2D_READ_MAXLEN) + block_read = W1_F2D_READ_MAXLEN; + else + block_read = todo; + + if (w1_f2d_readblock(sl, off, block_read, buf) < 0) + count = -EIO; + + todo -= W1_F2D_READ_MAXLEN; + buf += W1_F2D_READ_MAXLEN; + off += W1_F2D_READ_MAXLEN; + } + + mutex_unlock(&sl->master->bus_mutex); + + return count; +} + +/* + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be aligned at W1_F2D_SCRATCH_SIZE bytes and + * must be W1_F2D_SCRATCH_SIZE bytes long. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_F2D_PAGE_SIZE - (addr & W1_F2D_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f2d_write(struct w1_slave *sl, int addr, int len, const u8 *data) +{ + int tries = W1_F2D_READ_RETRIES; + u8 wrbuf[4]; + u8 rdbuf[W1_F2D_SCRATCH_SIZE + 3]; + u8 es = (addr + len - 1) % W1_F2D_SCRATCH_SIZE; + +retry: + + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F2D_WRITE_SCRATCH; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, 3); + w1_write_block(sl->master, data, len); + + /* Read the scratchpad and verify */ + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_8(sl->master, W1_F2D_READ_SCRATCH); + w1_read_block(sl->master, rdbuf, len + 3); + + /* Compare what was read against the data written */ + if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) || + (rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0)) { + + if (--tries) + goto retry; + + dev_err(&sl->dev, + "could not write to eeprom, scratchpad compare failed %d times\n", + W1_F2D_READ_RETRIES); + + return -1; + } + + /* Copy the scratchpad to EEPROM */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F2D_COPY_SCRATCH; + wrbuf[3] = es; + w1_write_block(sl->master, wrbuf, 4); + + /* Sleep for tprog ms to wait for the write to complete */ + msleep(W1_F2D_TPROG_MS); + + /* Reset the bus to wake up the EEPROM */ + w1_reset_bus(sl->master); + + return 0; +} + +static ssize_t eeprom_write(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); + int addr, len; + int copy; + + count = w1_f2d_fix_count(off, count, W1_F2D_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->bus_mutex); + + /* Can only write data in blocks of the size of the scratchpad */ + addr = off; + len = count; + while (len > 0) { + + /* if len too short or addr not aligned */ + if (len < W1_F2D_SCRATCH_SIZE || addr & W1_F2D_SCRATCH_MASK) { + char tmp[W1_F2D_SCRATCH_SIZE]; + + /* read the block and update the parts to be written */ + if (w1_f2d_readblock(sl, addr & ~W1_F2D_SCRATCH_MASK, + W1_F2D_SCRATCH_SIZE, tmp)) { + count = -EIO; + goto out_up; + } + + /* copy at most to the boundary of the PAGE or len */ + copy = W1_F2D_SCRATCH_SIZE - + (addr & W1_F2D_SCRATCH_MASK); + + if (copy > len) + copy = len; + + memcpy(&tmp[addr & W1_F2D_SCRATCH_MASK], buf, copy); + if (w1_f2d_write(sl, addr & ~W1_F2D_SCRATCH_MASK, + W1_F2D_SCRATCH_SIZE, tmp) < 0) { + count = -EIO; + goto out_up; + } + } else { + + copy = W1_F2D_SCRATCH_SIZE; + if (w1_f2d_write(sl, addr, copy, buf) < 0) { + count = -EIO; + goto out_up; + } + } + buf += copy; + addr += copy; + len -= copy; + } + +out_up: + mutex_unlock(&sl->master->bus_mutex); + + return count; +} + +static BIN_ATTR_RW(eeprom, W1_F2D_EEPROM_SIZE); + +static struct bin_attribute *w1_f2d_bin_attrs[] = { + &bin_attr_eeprom, + NULL, +}; + +static const struct attribute_group w1_f2d_group = { + .bin_attrs = w1_f2d_bin_attrs, +}; + +static const struct attribute_group *w1_f2d_groups[] = { + &w1_f2d_group, + NULL, +}; + +static struct w1_family_ops w1_f2d_fops = { + .groups = w1_f2d_groups, +}; + +static struct w1_family w1_family_2d = { + .fid = W1_EEPROM_DS2431, + .fops = &w1_f2d_fops, +}; +module_w1_family(w1_family_2d); + +MODULE_AUTHOR("Bernhard Weirich <bernhard.weirich@riedel.net>"); +MODULE_DESCRIPTION("w1 family 2d driver for DS2431, 1kb EEPROM"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_EEPROM_DS2431)); diff --git a/drivers/w1/slaves/w1_ds2433.c b/drivers/w1/slaves/w1_ds2433.c new file mode 100644 index 000000000..75ad70cfe --- /dev/null +++ b/drivers/w1/slaves/w1_ds2433.c @@ -0,0 +1,308 @@ +/* + * w1_ds2433.c - w1 family 23 (DS2433) driver + * + * Copyright (c) 2005 Ben Gardner <bgardner@wabtec.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> +#ifdef CONFIG_W1_SLAVE_DS2433_CRC +#include <linux/crc16.h> + +#define CRC16_INIT 0 +#define CRC16_VALID 0xb001 + +#endif + +#include <linux/w1.h> + +#define W1_EEPROM_DS2433 0x23 + +#define W1_EEPROM_SIZE 512 +#define W1_PAGE_COUNT 16 +#define W1_PAGE_SIZE 32 +#define W1_PAGE_BITS 5 +#define W1_PAGE_MASK 0x1F + +#define W1_F23_TIME 300 + +#define W1_F23_READ_EEPROM 0xF0 +#define W1_F23_WRITE_SCRATCH 0x0F +#define W1_F23_READ_SCRATCH 0xAA +#define W1_F23_COPY_SCRATCH 0x55 + +struct w1_f23_data { + u8 memory[W1_EEPROM_SIZE]; + u32 validcrc; +}; + +/** + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f23_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return (size - off); + + return count; +} + +#ifdef CONFIG_W1_SLAVE_DS2433_CRC +static int w1_f23_refresh_block(struct w1_slave *sl, struct w1_f23_data *data, + int block) +{ + u8 wrbuf[3]; + int off = block * W1_PAGE_SIZE; + + if (data->validcrc & (1 << block)) + return 0; + + if (w1_reset_select_slave(sl)) { + data->validcrc = 0; + return -EIO; + } + + wrbuf[0] = W1_F23_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, &data->memory[off], W1_PAGE_SIZE); + + /* cache the block if the CRC is valid */ + if (crc16(CRC16_INIT, &data->memory[off], W1_PAGE_SIZE) == CRC16_VALID) + data->validcrc |= (1 << block); + + return 0; +} +#endif /* CONFIG_W1_SLAVE_DS2433_CRC */ + +static ssize_t eeprom_read(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); +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + struct w1_f23_data *data = sl->family_data; + int i, min_page, max_page; +#else + u8 wrbuf[3]; +#endif + + if ((count = w1_f23_fix_count(off, count, W1_EEPROM_SIZE)) == 0) + return 0; + + mutex_lock(&sl->master->bus_mutex); + +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + + min_page = (off >> W1_PAGE_BITS); + max_page = (off + count - 1) >> W1_PAGE_BITS; + for (i = min_page; i <= max_page; i++) { + if (w1_f23_refresh_block(sl, data, i)) { + count = -EIO; + goto out_up; + } + } + memcpy(buf, &data->memory[off], count); + +#else /* CONFIG_W1_SLAVE_DS2433_CRC */ + + /* read directly from the EEPROM */ + if (w1_reset_select_slave(sl)) { + count = -EIO; + goto out_up; + } + + wrbuf[0] = W1_F23_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, buf, count); + +#endif /* CONFIG_W1_SLAVE_DS2433_CRC */ + +out_up: + mutex_unlock(&sl->master->bus_mutex); + + return count; +} + +/** + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be on one page. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_PAGE_SIZE - (addr & W1_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f23_write(struct w1_slave *sl, int addr, int len, const u8 *data) +{ +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + struct w1_f23_data *f23 = sl->family_data; +#endif + u8 wrbuf[4]; + u8 rdbuf[W1_PAGE_SIZE + 3]; + u8 es = (addr + len - 1) & 0x1f; + + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F23_WRITE_SCRATCH; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, 3); + w1_write_block(sl->master, data, len); + + /* Read the scratchpad and verify */ + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_8(sl->master, W1_F23_READ_SCRATCH); + w1_read_block(sl->master, rdbuf, len + 3); + + /* Compare what was read against the data written */ + if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) || + (rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0)) + return -1; + + /* Copy the scratchpad to EEPROM */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F23_COPY_SCRATCH; + wrbuf[3] = es; + w1_write_block(sl->master, wrbuf, 4); + + /* Sleep for 5 ms to wait for the write to complete */ + msleep(5); + + /* Reset the bus to wake up the EEPROM (this may not be needed) */ + w1_reset_bus(sl->master); +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + f23->validcrc &= ~(1 << (addr >> W1_PAGE_BITS)); +#endif + return 0; +} + +static ssize_t eeprom_write(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); + int addr, len, idx; + + if ((count = w1_f23_fix_count(off, count, W1_EEPROM_SIZE)) == 0) + return 0; + +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + /* can only write full blocks in cached mode */ + if ((off & W1_PAGE_MASK) || (count & W1_PAGE_MASK)) { + dev_err(&sl->dev, "invalid offset/count off=%d cnt=%zd\n", + (int)off, count); + return -EINVAL; + } + + /* make sure the block CRCs are valid */ + for (idx = 0; idx < count; idx += W1_PAGE_SIZE) { + if (crc16(CRC16_INIT, &buf[idx], W1_PAGE_SIZE) != CRC16_VALID) { + dev_err(&sl->dev, "bad CRC at offset %d\n", (int)off); + return -EINVAL; + } + } +#endif /* CONFIG_W1_SLAVE_DS2433_CRC */ + + mutex_lock(&sl->master->bus_mutex); + + /* Can only write data to one page at a time */ + idx = 0; + while (idx < count) { + addr = off + idx; + len = W1_PAGE_SIZE - (addr & W1_PAGE_MASK); + if (len > (count - idx)) + len = count - idx; + + if (w1_f23_write(sl, addr, len, &buf[idx]) < 0) { + count = -EIO; + goto out_up; + } + idx += len; + } + +out_up: + mutex_unlock(&sl->master->bus_mutex); + + return count; +} + +static BIN_ATTR_RW(eeprom, W1_EEPROM_SIZE); + +static struct bin_attribute *w1_f23_bin_attributes[] = { + &bin_attr_eeprom, + NULL, +}; + +static const struct attribute_group w1_f23_group = { + .bin_attrs = w1_f23_bin_attributes, +}; + +static const struct attribute_group *w1_f23_groups[] = { + &w1_f23_group, + NULL, +}; + +static int w1_f23_add_slave(struct w1_slave *sl) +{ +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + struct w1_f23_data *data; + + data = kzalloc(sizeof(struct w1_f23_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + sl->family_data = data; + +#endif /* CONFIG_W1_SLAVE_DS2433_CRC */ + return 0; +} + +static void w1_f23_remove_slave(struct w1_slave *sl) +{ +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + kfree(sl->family_data); + sl->family_data = NULL; +#endif /* CONFIG_W1_SLAVE_DS2433_CRC */ +} + +static struct w1_family_ops w1_f23_fops = { + .add_slave = w1_f23_add_slave, + .remove_slave = w1_f23_remove_slave, + .groups = w1_f23_groups, +}; + +static struct w1_family w1_family_23 = { + .fid = W1_EEPROM_DS2433, + .fops = &w1_f23_fops, +}; +module_w1_family(w1_family_23); + +MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>"); +MODULE_DESCRIPTION("w1 family 23 driver for DS2433, 4kb EEPROM"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_EEPROM_DS2433)); diff --git a/drivers/w1/slaves/w1_ds2438.c b/drivers/w1/slaves/w1_ds2438.c new file mode 100644 index 000000000..b005dda9c --- /dev/null +++ b/drivers/w1/slaves/w1_ds2438.c @@ -0,0 +1,430 @@ +/* + * 1-Wire implementation for the ds2438 chip + * + * Copyright (c) 2017 Mariusz Bialonczyk <manio@skyboo.net> + * + * 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/device.h> +#include <linux/types.h> +#include <linux/delay.h> + +#include <linux/w1.h> + +#define W1_FAMILY_DS2438 0x26 + +#define W1_DS2438_RETRIES 3 + +/* Memory commands */ +#define W1_DS2438_READ_SCRATCH 0xBE +#define W1_DS2438_WRITE_SCRATCH 0x4E +#define W1_DS2438_COPY_SCRATCH 0x48 +#define W1_DS2438_RECALL_MEMORY 0xB8 +/* Register commands */ +#define W1_DS2438_CONVERT_TEMP 0x44 +#define W1_DS2438_CONVERT_VOLTAGE 0xB4 + +#define DS2438_PAGE_SIZE 8 +#define DS2438_ADC_INPUT_VAD 0 +#define DS2438_ADC_INPUT_VDD 1 +#define DS2438_MAX_CONVERSION_TIME 10 /* ms */ + +/* Page #0 definitions */ +#define DS2438_STATUS_REG 0x00 /* Status/Configuration Register */ +#define DS2438_STATUS_IAD (1 << 0) /* Current A/D Control Bit */ +#define DS2438_STATUS_CA (1 << 1) /* Current Accumulator Configuration */ +#define DS2438_STATUS_EE (1 << 2) /* Current Accumulator Shadow Selector bit */ +#define DS2438_STATUS_AD (1 << 3) /* Voltage A/D Input Select Bit */ +#define DS2438_STATUS_TB (1 << 4) /* Temperature Busy Flag */ +#define DS2438_STATUS_NVB (1 << 5) /* Nonvolatile Memory Busy Flag */ +#define DS2438_STATUS_ADB (1 << 6) /* A/D Converter Busy Flag */ + +#define DS2438_TEMP_LSB 0x01 +#define DS2438_TEMP_MSB 0x02 +#define DS2438_VOLTAGE_LSB 0x03 +#define DS2438_VOLTAGE_MSB 0x04 +#define DS2438_CURRENT_LSB 0x05 +#define DS2438_CURRENT_MSB 0x06 +#define DS2438_THRESHOLD 0x07 + +static int w1_ds2438_get_page(struct w1_slave *sl, int pageno, u8 *buf) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[2]; + u8 crc; + size_t count; + + while (retries--) { + crc = 0; + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_RECALL_MEMORY; + w1_buf[1] = (u8)pageno; + w1_write_block(sl->master, w1_buf, 2); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_READ_SCRATCH; + w1_buf[1] = (u8)pageno; + w1_write_block(sl->master, w1_buf, 2); + + count = w1_read_block(sl->master, buf, DS2438_PAGE_SIZE + 1); + if (count == DS2438_PAGE_SIZE + 1) { + crc = w1_calc_crc8(buf, DS2438_PAGE_SIZE); + + /* check for correct CRC */ + if ((u8)buf[DS2438_PAGE_SIZE] == crc) + return 0; + } + } + return -1; +} + +static int w1_ds2438_get_temperature(struct w1_slave *sl, int16_t *temperature) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + unsigned int tm = DS2438_MAX_CONVERSION_TIME; + unsigned long sleep_rem; + int ret; + + mutex_lock(&sl->master->bus_mutex); + + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_write_8(sl->master, W1_DS2438_CONVERT_TEMP); + + mutex_unlock(&sl->master->bus_mutex); + sleep_rem = msleep_interruptible(tm); + if (sleep_rem != 0) { + ret = -1; + goto post_unlock; + } + + if (mutex_lock_interruptible(&sl->master->bus_mutex) != 0) { + ret = -1; + goto post_unlock; + } + + break; + } + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + *temperature = (((int16_t) w1_buf[DS2438_TEMP_MSB]) << 8) | ((uint16_t) w1_buf[DS2438_TEMP_LSB]); + ret = 0; + } else + ret = -1; + + mutex_unlock(&sl->master->bus_mutex); + +post_unlock: + return ret; +} + +static int w1_ds2438_change_config_bit(struct w1_slave *sl, u8 mask, u8 value) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[3]; + u8 status; + int perform_write = 0; + + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_RECALL_MEMORY; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_READ_SCRATCH; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + /* reading one byte of result */ + status = w1_read_8(sl->master); + + /* if bit0=1, set a value to a mask for easy compare */ + if (value) + value = mask; + + if ((status & mask) == value) + return 0; /* already set as requested */ + else { + /* changing bit */ + status ^= mask; + perform_write = 1; + } + break; + } + + if (perform_write) { + retries = W1_DS2438_RETRIES; + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_WRITE_SCRATCH; + w1_buf[1] = 0x00; + w1_buf[2] = status; + w1_write_block(sl->master, w1_buf, 3); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_COPY_SCRATCH; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + return 0; + } + } + return -1; +} + +static int w1_ds2438_get_voltage(struct w1_slave *sl, + int adc_input, uint16_t *voltage) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + unsigned int tm = DS2438_MAX_CONVERSION_TIME; + unsigned long sleep_rem; + int ret; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_change_config_bit(sl, DS2438_STATUS_AD, adc_input)) { + ret = -1; + goto pre_unlock; + } + + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_write_8(sl->master, W1_DS2438_CONVERT_VOLTAGE); + + mutex_unlock(&sl->master->bus_mutex); + sleep_rem = msleep_interruptible(tm); + if (sleep_rem != 0) { + ret = -1; + goto post_unlock; + } + + if (mutex_lock_interruptible(&sl->master->bus_mutex) != 0) { + ret = -1; + goto post_unlock; + } + + break; + } + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + *voltage = (((uint16_t) w1_buf[DS2438_VOLTAGE_MSB]) << 8) | ((uint16_t) w1_buf[DS2438_VOLTAGE_LSB]); + ret = 0; + } else + ret = -1; + +pre_unlock: + mutex_unlock(&sl->master->bus_mutex); + +post_unlock: + return ret; +} + +static int w1_ds2438_get_current(struct w1_slave *sl, int16_t *voltage) +{ + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + int ret; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + /* The voltage measured across current sense resistor RSENS. */ + *voltage = (((int16_t) w1_buf[DS2438_CURRENT_MSB]) << 8) | ((int16_t) w1_buf[DS2438_CURRENT_LSB]); + ret = 0; + } else + ret = -1; + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} + +static ssize_t iad_write(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); + int ret; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_change_config_bit(sl, DS2438_STATUS_IAD, *buf & 0x01) == 0) + ret = 1; + else + ret = -EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} + +static ssize_t iad_read(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); + int ret; + int16_t voltage; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_current(sl, &voltage) == 0) { + ret = snprintf(buf, count, "%i\n", voltage); + } else + ret = -EIO; + + return ret; +} + +static ssize_t page0_read(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); + int ret; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + + /* Read no more than page0 size */ + if (count > DS2438_PAGE_SIZE) + count = DS2438_PAGE_SIZE; + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + memcpy(buf, &w1_buf, count); + ret = count; + } else + ret = -EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} + +static ssize_t temperature_read(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); + int ret; + int16_t temp; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_temperature(sl, &temp) == 0) { + ret = snprintf(buf, count, "%i\n", temp); + } else + ret = -EIO; + + return ret; +} + +static ssize_t vad_read(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); + int ret; + uint16_t voltage; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_voltage(sl, DS2438_ADC_INPUT_VAD, &voltage) == 0) { + ret = snprintf(buf, count, "%u\n", voltage); + } else + ret = -EIO; + + return ret; +} + +static ssize_t vdd_read(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); + int ret; + uint16_t voltage; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_voltage(sl, DS2438_ADC_INPUT_VDD, &voltage) == 0) { + ret = snprintf(buf, count, "%u\n", voltage); + } else + ret = -EIO; + + return ret; +} + +static BIN_ATTR(iad, S_IRUGO | S_IWUSR | S_IWGRP, iad_read, iad_write, 0); +static BIN_ATTR_RO(page0, DS2438_PAGE_SIZE); +static BIN_ATTR_RO(temperature, 0/* real length varies */); +static BIN_ATTR_RO(vad, 0/* real length varies */); +static BIN_ATTR_RO(vdd, 0/* real length varies */); + +static struct bin_attribute *w1_ds2438_bin_attrs[] = { + &bin_attr_iad, + &bin_attr_page0, + &bin_attr_temperature, + &bin_attr_vad, + &bin_attr_vdd, + NULL, +}; + +static const struct attribute_group w1_ds2438_group = { + .bin_attrs = w1_ds2438_bin_attrs, +}; + +static const struct attribute_group *w1_ds2438_groups[] = { + &w1_ds2438_group, + NULL, +}; + +static struct w1_family_ops w1_ds2438_fops = { + .groups = w1_ds2438_groups, +}; + +static struct w1_family w1_ds2438_family = { + .fid = W1_FAMILY_DS2438, + .fops = &w1_ds2438_fops, +}; +module_w1_family(w1_ds2438_family); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mariusz Bialonczyk <manio@skyboo.net>"); +MODULE_DESCRIPTION("1-wire driver for Maxim/Dallas DS2438 Smart Battery Monitor"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2438)); diff --git a/drivers/w1/slaves/w1_ds2780.c b/drivers/w1/slaves/w1_ds2780.c new file mode 100644 index 000000000..a60528131 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2780.c @@ -0,0 +1,163 @@ +/* + * 1-Wire implementation for the ds2780 chip + * + * Copyright (C) 2010 Indesign, LLC + * + * Author: Clifton Barnes <cabarnes@indesign-llc.com> + * + * Based on w1-ds2760 driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/idr.h> + +#include <linux/w1.h> + +#include "w1_ds2780.h" + +#define W1_FAMILY_DS2780 0x32 + +static int w1_ds2780_do_io(struct device *dev, char *buf, int addr, + size_t count, int io) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (addr > DS2780_DATA_SIZE || addr < 0) + return 0; + + count = min_t(int, count, DS2780_DATA_SIZE - addr); + + if (w1_reset_select_slave(sl) == 0) { + if (io) { + w1_write_8(sl->master, W1_DS2780_WRITE_DATA); + w1_write_8(sl->master, addr); + w1_write_block(sl->master, buf, count); + } else { + w1_write_8(sl->master, W1_DS2780_READ_DATA); + w1_write_8(sl->master, addr); + count = w1_read_block(sl->master, buf, count); + } + } + + return count; +} + +int w1_ds2780_io(struct device *dev, char *buf, int addr, size_t count, + int io) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + int ret; + + if (!dev) + return -ENODEV; + + mutex_lock(&sl->master->bus_mutex); + + ret = w1_ds2780_do_io(dev, buf, addr, count, io); + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} +EXPORT_SYMBOL(w1_ds2780_io); + +int w1_ds2780_eeprom_cmd(struct device *dev, int addr, int cmd) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (!dev) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl) == 0) { + w1_write_8(sl->master, cmd); + w1_write_8(sl->master, addr); + } + + mutex_unlock(&sl->master->bus_mutex); + return 0; +} +EXPORT_SYMBOL(w1_ds2780_eeprom_cmd); + +static ssize_t w1_slave_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + return w1_ds2780_io(dev, buf, off, count, 0); +} + +static BIN_ATTR_RO(w1_slave, DS2780_DATA_SIZE); + +static struct bin_attribute *w1_ds2780_bin_attrs[] = { + &bin_attr_w1_slave, + NULL, +}; + +static const struct attribute_group w1_ds2780_group = { + .bin_attrs = w1_ds2780_bin_attrs, +}; + +static const struct attribute_group *w1_ds2780_groups[] = { + &w1_ds2780_group, + NULL, +}; + +static int w1_ds2780_add_slave(struct w1_slave *sl) +{ + int ret; + struct platform_device *pdev; + + pdev = platform_device_alloc("ds2780-battery", PLATFORM_DEVID_AUTO); + if (!pdev) + return -ENOMEM; + pdev->dev.parent = &sl->dev; + + ret = platform_device_add(pdev); + if (ret) + goto pdev_add_failed; + + dev_set_drvdata(&sl->dev, pdev); + + return 0; + +pdev_add_failed: + platform_device_put(pdev); + + return ret; +} + +static void w1_ds2780_remove_slave(struct w1_slave *sl) +{ + struct platform_device *pdev = dev_get_drvdata(&sl->dev); + + platform_device_unregister(pdev); +} + +static struct w1_family_ops w1_ds2780_fops = { + .add_slave = w1_ds2780_add_slave, + .remove_slave = w1_ds2780_remove_slave, + .groups = w1_ds2780_groups, +}; + +static struct w1_family w1_ds2780_family = { + .fid = W1_FAMILY_DS2780, + .fops = &w1_ds2780_fops, +}; +module_w1_family(w1_ds2780_family); + +MODULE_AUTHOR("Clifton Barnes <cabarnes@indesign-llc.com>"); +MODULE_DESCRIPTION("1-wire Driver for Maxim/Dallas DS2780 Stand-Alone Fuel Gauge IC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2780)); diff --git a/drivers/w1/slaves/w1_ds2780.h b/drivers/w1/slaves/w1_ds2780.h new file mode 100644 index 000000000..a1fba79eb --- /dev/null +++ b/drivers/w1/slaves/w1_ds2780.h @@ -0,0 +1,129 @@ +/* + * 1-Wire implementation for the ds2780 chip + * + * Copyright (C) 2010 Indesign, LLC + * + * Author: Clifton Barnes <cabarnes@indesign-llc.com> + * + * Based on w1-ds2760 driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _W1_DS2780_H +#define _W1_DS2780_H + +/* Function commands */ +#define W1_DS2780_READ_DATA 0x69 +#define W1_DS2780_WRITE_DATA 0x6C +#define W1_DS2780_COPY_DATA 0x48 +#define W1_DS2780_RECALL_DATA 0xB8 +#define W1_DS2780_LOCK 0x6A + +/* Register map */ +/* Register 0x00 Reserved */ +#define DS2780_STATUS_REG 0x01 +#define DS2780_RAAC_MSB_REG 0x02 +#define DS2780_RAAC_LSB_REG 0x03 +#define DS2780_RSAC_MSB_REG 0x04 +#define DS2780_RSAC_LSB_REG 0x05 +#define DS2780_RARC_REG 0x06 +#define DS2780_RSRC_REG 0x07 +#define DS2780_IAVG_MSB_REG 0x08 +#define DS2780_IAVG_LSB_REG 0x09 +#define DS2780_TEMP_MSB_REG 0x0A +#define DS2780_TEMP_LSB_REG 0x0B +#define DS2780_VOLT_MSB_REG 0x0C +#define DS2780_VOLT_LSB_REG 0x0D +#define DS2780_CURRENT_MSB_REG 0x0E +#define DS2780_CURRENT_LSB_REG 0x0F +#define DS2780_ACR_MSB_REG 0x10 +#define DS2780_ACR_LSB_REG 0x11 +#define DS2780_ACRL_MSB_REG 0x12 +#define DS2780_ACRL_LSB_REG 0x13 +#define DS2780_AS_REG 0x14 +#define DS2780_SFR_REG 0x15 +#define DS2780_FULL_MSB_REG 0x16 +#define DS2780_FULL_LSB_REG 0x17 +#define DS2780_AE_MSB_REG 0x18 +#define DS2780_AE_LSB_REG 0x19 +#define DS2780_SE_MSB_REG 0x1A +#define DS2780_SE_LSB_REG 0x1B +/* Register 0x1C - 0x1E Reserved */ +#define DS2780_EEPROM_REG 0x1F +#define DS2780_EEPROM_BLOCK0_START 0x20 +/* Register 0x20 - 0x2F User EEPROM */ +#define DS2780_EEPROM_BLOCK0_END 0x2F +/* Register 0x30 - 0x5F Reserved */ +#define DS2780_EEPROM_BLOCK1_START 0x60 +#define DS2780_CONTROL_REG 0x60 +#define DS2780_AB_REG 0x61 +#define DS2780_AC_MSB_REG 0x62 +#define DS2780_AC_LSB_REG 0x63 +#define DS2780_VCHG_REG 0x64 +#define DS2780_IMIN_REG 0x65 +#define DS2780_VAE_REG 0x66 +#define DS2780_IAE_REG 0x67 +#define DS2780_AE_40_REG 0x68 +#define DS2780_RSNSP_REG 0x69 +#define DS2780_FULL_40_MSB_REG 0x6A +#define DS2780_FULL_40_LSB_REG 0x6B +#define DS2780_FULL_3040_SLOPE_REG 0x6C +#define DS2780_FULL_2030_SLOPE_REG 0x6D +#define DS2780_FULL_1020_SLOPE_REG 0x6E +#define DS2780_FULL_0010_SLOPE_REG 0x6F +#define DS2780_AE_3040_SLOPE_REG 0x70 +#define DS2780_AE_2030_SLOPE_REG 0x71 +#define DS2780_AE_1020_SLOPE_REG 0x72 +#define DS2780_AE_0010_SLOPE_REG 0x73 +#define DS2780_SE_3040_SLOPE_REG 0x74 +#define DS2780_SE_2030_SLOPE_REG 0x75 +#define DS2780_SE_1020_SLOPE_REG 0x76 +#define DS2780_SE_0010_SLOPE_REG 0x77 +#define DS2780_RSGAIN_MSB_REG 0x78 +#define DS2780_RSGAIN_LSB_REG 0x79 +#define DS2780_RSTC_REG 0x7A +#define DS2780_FRSGAIN_MSB_REG 0x7B +#define DS2780_FRSGAIN_LSB_REG 0x7C +#define DS2780_EEPROM_BLOCK1_END 0x7C +/* Register 0x7D - 0xFF Reserved */ + +/* Number of valid register addresses */ +#define DS2780_DATA_SIZE 0x80 + +/* Status register bits */ +#define DS2780_STATUS_REG_CHGTF (1 << 7) +#define DS2780_STATUS_REG_AEF (1 << 6) +#define DS2780_STATUS_REG_SEF (1 << 5) +#define DS2780_STATUS_REG_LEARNF (1 << 4) +/* Bit 3 Reserved */ +#define DS2780_STATUS_REG_UVF (1 << 2) +#define DS2780_STATUS_REG_PORF (1 << 1) +/* Bit 0 Reserved */ + +/* Control register bits */ +/* Bit 7 Reserved */ +#define DS2780_CONTROL_REG_UVEN (1 << 6) +#define DS2780_CONTROL_REG_PMOD (1 << 5) +#define DS2780_CONTROL_REG_RNAOP (1 << 4) +/* Bit 0 - 3 Reserved */ + +/* Special feature register bits */ +/* Bit 1 - 7 Reserved */ +#define DS2780_SFR_REG_PIOSC (1 << 0) + +/* EEPROM register bits */ +#define DS2780_EEPROM_REG_EEC (1 << 7) +#define DS2780_EEPROM_REG_LOCK (1 << 6) +/* Bit 2 - 6 Reserved */ +#define DS2780_EEPROM_REG_BL1 (1 << 1) +#define DS2780_EEPROM_REG_BL0 (1 << 0) + +extern int w1_ds2780_io(struct device *dev, char *buf, int addr, size_t count, + int io); +extern int w1_ds2780_eeprom_cmd(struct device *dev, int addr, int cmd); + +#endif /* !_W1_DS2780_H */ diff --git a/drivers/w1/slaves/w1_ds2781.c b/drivers/w1/slaves/w1_ds2781.c new file mode 100644 index 000000000..645be6e0b --- /dev/null +++ b/drivers/w1/slaves/w1_ds2781.c @@ -0,0 +1,160 @@ +/* + * 1-Wire implementation for the ds2781 chip + * + * Author: Renata Sayakhova <renata@oktetlabs.ru> + * + * Based on w1-ds2780 driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> + +#include <linux/w1.h> + +#include "w1_ds2781.h" + +#define W1_FAMILY_DS2781 0x3D + +static int w1_ds2781_do_io(struct device *dev, char *buf, int addr, + size_t count, int io) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (addr > DS2781_DATA_SIZE || addr < 0) + return 0; + + count = min_t(int, count, DS2781_DATA_SIZE - addr); + + if (w1_reset_select_slave(sl) == 0) { + if (io) { + w1_write_8(sl->master, W1_DS2781_WRITE_DATA); + w1_write_8(sl->master, addr); + w1_write_block(sl->master, buf, count); + } else { + w1_write_8(sl->master, W1_DS2781_READ_DATA); + w1_write_8(sl->master, addr); + count = w1_read_block(sl->master, buf, count); + } + } + + return count; +} + +int w1_ds2781_io(struct device *dev, char *buf, int addr, size_t count, + int io) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + int ret; + + if (!dev) + return -ENODEV; + + mutex_lock(&sl->master->bus_mutex); + + ret = w1_ds2781_do_io(dev, buf, addr, count, io); + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} +EXPORT_SYMBOL(w1_ds2781_io); + +int w1_ds2781_eeprom_cmd(struct device *dev, int addr, int cmd) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (!dev) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl) == 0) { + w1_write_8(sl->master, cmd); + w1_write_8(sl->master, addr); + } + + mutex_unlock(&sl->master->bus_mutex); + return 0; +} +EXPORT_SYMBOL(w1_ds2781_eeprom_cmd); + +static ssize_t w1_slave_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + return w1_ds2781_io(dev, buf, off, count, 0); +} + +static BIN_ATTR_RO(w1_slave, DS2781_DATA_SIZE); + +static struct bin_attribute *w1_ds2781_bin_attrs[] = { + &bin_attr_w1_slave, + NULL, +}; + +static const struct attribute_group w1_ds2781_group = { + .bin_attrs = w1_ds2781_bin_attrs, +}; + +static const struct attribute_group *w1_ds2781_groups[] = { + &w1_ds2781_group, + NULL, +}; + +static int w1_ds2781_add_slave(struct w1_slave *sl) +{ + int ret; + struct platform_device *pdev; + + pdev = platform_device_alloc("ds2781-battery", PLATFORM_DEVID_AUTO); + if (!pdev) + return -ENOMEM; + pdev->dev.parent = &sl->dev; + + ret = platform_device_add(pdev); + if (ret) + goto pdev_add_failed; + + dev_set_drvdata(&sl->dev, pdev); + + return 0; + +pdev_add_failed: + platform_device_put(pdev); + + return ret; +} + +static void w1_ds2781_remove_slave(struct w1_slave *sl) +{ + struct platform_device *pdev = dev_get_drvdata(&sl->dev); + + platform_device_unregister(pdev); +} + +static struct w1_family_ops w1_ds2781_fops = { + .add_slave = w1_ds2781_add_slave, + .remove_slave = w1_ds2781_remove_slave, + .groups = w1_ds2781_groups, +}; + +static struct w1_family w1_ds2781_family = { + .fid = W1_FAMILY_DS2781, + .fops = &w1_ds2781_fops, +}; +module_w1_family(w1_ds2781_family); + +MODULE_AUTHOR("Renata Sayakhova <renata@oktetlabs.ru>"); +MODULE_DESCRIPTION("1-wire Driver for Maxim/Dallas DS2781 Stand-Alone Fuel Gauge IC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2781)); diff --git a/drivers/w1/slaves/w1_ds2781.h b/drivers/w1/slaves/w1_ds2781.h new file mode 100644 index 000000000..557dfb0b4 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2781.h @@ -0,0 +1,134 @@ +/* + * 1-Wire implementation for the ds2780 chip + * + * Author: Renata Sayakhova <renata@oktetlabs.ru> + * + * Based on w1-ds2760 driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _W1_DS2781_H +#define _W1_DS2781_H + +/* Function commands */ +#define W1_DS2781_READ_DATA 0x69 +#define W1_DS2781_WRITE_DATA 0x6C +#define W1_DS2781_COPY_DATA 0x48 +#define W1_DS2781_RECALL_DATA 0xB8 +#define W1_DS2781_LOCK 0x6A + +/* Register map */ +/* Register 0x00 Reserved */ +#define DS2781_STATUS 0x01 +#define DS2781_RAAC_MSB 0x02 +#define DS2781_RAAC_LSB 0x03 +#define DS2781_RSAC_MSB 0x04 +#define DS2781_RSAC_LSB 0x05 +#define DS2781_RARC 0x06 +#define DS2781_RSRC 0x07 +#define DS2781_IAVG_MSB 0x08 +#define DS2781_IAVG_LSB 0x09 +#define DS2781_TEMP_MSB 0x0A +#define DS2781_TEMP_LSB 0x0B +#define DS2781_VOLT_MSB 0x0C +#define DS2781_VOLT_LSB 0x0D +#define DS2781_CURRENT_MSB 0x0E +#define DS2781_CURRENT_LSB 0x0F +#define DS2781_ACR_MSB 0x10 +#define DS2781_ACR_LSB 0x11 +#define DS2781_ACRL_MSB 0x12 +#define DS2781_ACRL_LSB 0x13 +#define DS2781_AS 0x14 +#define DS2781_SFR 0x15 +#define DS2781_FULL_MSB 0x16 +#define DS2781_FULL_LSB 0x17 +#define DS2781_AE_MSB 0x18 +#define DS2781_AE_LSB 0x19 +#define DS2781_SE_MSB 0x1A +#define DS2781_SE_LSB 0x1B +/* Register 0x1C - 0x1E Reserved */ +#define DS2781_EEPROM 0x1F +#define DS2781_EEPROM_BLOCK0_START 0x20 +/* Register 0x20 - 0x2F User EEPROM */ +#define DS2781_EEPROM_BLOCK0_END 0x2F +/* Register 0x30 - 0x5F Reserved */ +#define DS2781_EEPROM_BLOCK1_START 0x60 +#define DS2781_CONTROL 0x60 +#define DS2781_AB 0x61 +#define DS2781_AC_MSB 0x62 +#define DS2781_AC_LSB 0x63 +#define DS2781_VCHG 0x64 +#define DS2781_IMIN 0x65 +#define DS2781_VAE 0x66 +#define DS2781_IAE 0x67 +#define DS2781_AE_40 0x68 +#define DS2781_RSNSP 0x69 +#define DS2781_FULL_40_MSB 0x6A +#define DS2781_FULL_40_LSB 0x6B +#define DS2781_FULL_4_SLOPE 0x6C +#define DS2781_FULL_3_SLOPE 0x6D +#define DS2781_FULL_2_SLOPE 0x6E +#define DS2781_FULL_1_SLOPE 0x6F +#define DS2781_AE_4_SLOPE 0x70 +#define DS2781_AE_3_SLOPE 0x71 +#define DS2781_AE_2_SLOPE 0x72 +#define DS2781_AE_1_SLOPE 0x73 +#define DS2781_SE_4_SLOPE 0x74 +#define DS2781_SE_3_SLOPE 0x75 +#define DS2781_SE_2_SLOPE 0x76 +#define DS2781_SE_1_SLOPE 0x77 +#define DS2781_RSGAIN_MSB 0x78 +#define DS2781_RSGAIN_LSB 0x79 +#define DS2781_RSTC 0x7A +#define DS2781_COB 0x7B +#define DS2781_TBP34 0x7C +#define DS2781_TBP23 0x7D +#define DS2781_TBP12 0x7E +#define DS2781_EEPROM_BLOCK1_END 0x7F +/* Register 0x7D - 0xFF Reserved */ + +#define DS2781_FSGAIN_MSB 0xB0 +#define DS2781_FSGAIN_LSB 0xB1 + +/* Number of valid register addresses */ +#define DS2781_DATA_SIZE 0xB2 + +/* Status register bits */ +#define DS2781_STATUS_CHGTF (1 << 7) +#define DS2781_STATUS_AEF (1 << 6) +#define DS2781_STATUS_SEF (1 << 5) +#define DS2781_STATUS_LEARNF (1 << 4) +/* Bit 3 Reserved */ +#define DS2781_STATUS_UVF (1 << 2) +#define DS2781_STATUS_PORF (1 << 1) +/* Bit 0 Reserved */ + +/* Control register bits */ +/* Bit 7 Reserved */ +#define DS2781_CONTROL_NBEN (1 << 7) +#define DS2781_CONTROL_UVEN (1 << 6) +#define DS2781_CONTROL_PMOD (1 << 5) +#define DS2781_CONTROL_RNAOP (1 << 4) +#define DS1781_CONTROL_UVTH (1 << 3) +/* Bit 0 - 2 Reserved */ + +/* Special feature register bits */ +/* Bit 1 - 7 Reserved */ +#define DS2781_SFR_PIOSC (1 << 0) + +/* EEPROM register bits */ +#define DS2781_EEPROM_EEC (1 << 7) +#define DS2781_EEPROM_LOCK (1 << 6) +/* Bit 2 - 6 Reserved */ +#define DS2781_EEPROM_BL1 (1 << 1) +#define DS2781_EEPROM_BL0 (1 << 0) + +extern int w1_ds2781_io(struct device *dev, char *buf, int addr, size_t count, + int io); +extern int w1_ds2781_eeprom_cmd(struct device *dev, int addr, int cmd); + +#endif /* !_W1_DS2781_H */ diff --git a/drivers/w1/slaves/w1_ds2805.c b/drivers/w1/slaves/w1_ds2805.c new file mode 100644 index 000000000..29348d283 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2805.c @@ -0,0 +1,313 @@ +/* + * w1_ds2805 - w1 family 0d (DS28E05) driver + * + * Copyright (c) 2016 Andrew Worsley amworsley@gmail.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/w1.h> + +#define W1_EEPROM_DS2805 0x0D + +#define W1_F0D_EEPROM_SIZE 128 +#define W1_F0D_PAGE_BITS 3 +#define W1_F0D_PAGE_SIZE (1<<W1_F0D_PAGE_BITS) +#define W1_F0D_PAGE_MASK 0x0F + +#define W1_F0D_SCRATCH_BITS 1 +#define W1_F0D_SCRATCH_SIZE (1<<W1_F0D_SCRATCH_BITS) +#define W1_F0D_SCRATCH_MASK (W1_F0D_SCRATCH_SIZE-1) + +#define W1_F0D_READ_EEPROM 0xF0 +#define W1_F0D_WRITE_EEPROM 0x55 +#define W1_F0D_RELEASE 0xFF + +#define W1_F0D_CS_OK 0xAA /* Chip Status Ok */ + +#define W1_F0D_TPROG_MS 16 + +#define W1_F0D_READ_RETRIES 10 +#define W1_F0D_READ_MAXLEN W1_F0D_EEPROM_SIZE + +/* + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f0d_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return size - off; + + return count; +} + +/* + * Read a block from W1 ROM two times and compares the results. + * If they are equal they are returned, otherwise the read + * is repeated W1_F0D_READ_RETRIES times. + * + * count must not exceed W1_F0D_READ_MAXLEN. + */ +static int w1_f0d_readblock(struct w1_slave *sl, int off, int count, char *buf) +{ + u8 wrbuf[3]; + u8 cmp[W1_F0D_READ_MAXLEN]; + int tries = W1_F0D_READ_RETRIES; + + do { + wrbuf[0] = W1_F0D_READ_EEPROM; + wrbuf[1] = off & 0x7f; + wrbuf[2] = 0; + + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_block(sl->master, wrbuf, sizeof(wrbuf)); + w1_read_block(sl->master, buf, count); + + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_block(sl->master, wrbuf, sizeof(wrbuf)); + w1_read_block(sl->master, cmp, count); + + if (!memcmp(cmp, buf, count)) + return 0; + } while (--tries); + + dev_err(&sl->dev, "proof reading failed %d times\n", + W1_F0D_READ_RETRIES); + + return -1; +} + +static ssize_t w1_f0d_read_bin(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); + int todo = count; + + count = w1_f0d_fix_count(off, count, W1_F0D_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->mutex); + + /* read directly from the EEPROM in chunks of W1_F0D_READ_MAXLEN */ + while (todo > 0) { + int block_read; + + if (todo >= W1_F0D_READ_MAXLEN) + block_read = W1_F0D_READ_MAXLEN; + else + block_read = todo; + + if (w1_f0d_readblock(sl, off, block_read, buf) < 0) { + count = -EIO; + break; + } + + todo -= W1_F0D_READ_MAXLEN; + buf += W1_F0D_READ_MAXLEN; + off += W1_F0D_READ_MAXLEN; + } + + mutex_unlock(&sl->master->mutex); + + return count; +} + +/* + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be aligned at W1_F0D_SCRATCH_SIZE bytes and + * must be W1_F0D_SCRATCH_SIZE bytes long. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_F0D_PAGE_SIZE - (addr & W1_F0D_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f0d_write(struct w1_slave *sl, int addr, int len, const u8 *data) +{ + int tries = W1_F0D_READ_RETRIES; + u8 wrbuf[3]; + u8 rdbuf[W1_F0D_SCRATCH_SIZE]; + u8 cs; + + if ((addr & 1) || (len != 2)) { + dev_err(&sl->dev, "%s: bad addr/len - addr=%#x len=%d\n", + __func__, addr, len); + return -1; + } + +retry: + + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F0D_WRITE_EEPROM; + wrbuf[1] = addr & 0xff; + wrbuf[2] = 0xff; /* ?? from Example */ + + w1_write_block(sl->master, wrbuf, sizeof(wrbuf)); + w1_write_block(sl->master, data, len); + + w1_read_block(sl->master, rdbuf, sizeof(rdbuf)); + /* Compare what was read against the data written */ + if ((rdbuf[0] != data[0]) || (rdbuf[1] != data[1])) { + + if (--tries) + goto retry; + + dev_err(&sl->dev, + "could not write to eeprom, scratchpad compare failed %d times\n", + W1_F0D_READ_RETRIES); + pr_info("%s: rdbuf = %#x %#x data = %#x %#x\n", + __func__, rdbuf[0], rdbuf[1], data[0], data[1]); + + return -1; + } + + /* Trigger write out to EEPROM */ + w1_write_8(sl->master, W1_F0D_RELEASE); + + /* Sleep for tprog ms to wait for the write to complete */ + msleep(W1_F0D_TPROG_MS); + + /* Check CS (Command Status) == 0xAA ? */ + cs = w1_read_8(sl->master); + if (cs != W1_F0D_CS_OK) { + dev_err(&sl->dev, "save to eeprom failed = CS=%#x\n", cs); + return -1; + } + + return 0; +} + +static ssize_t w1_f0d_write_bin(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); + int addr, len; + int copy; + + count = w1_f0d_fix_count(off, count, W1_F0D_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->mutex); + + /* Can only write data in blocks of the size of the scratchpad */ + addr = off; + len = count; + while (len > 0) { + + /* if len too short or addr not aligned */ + if (len < W1_F0D_SCRATCH_SIZE || addr & W1_F0D_SCRATCH_MASK) { + char tmp[W1_F0D_SCRATCH_SIZE]; + + /* read the block and update the parts to be written */ + if (w1_f0d_readblock(sl, addr & ~W1_F0D_SCRATCH_MASK, + W1_F0D_SCRATCH_SIZE, tmp)) { + count = -EIO; + goto out_up; + } + + /* copy at most to the boundary of the PAGE or len */ + copy = W1_F0D_SCRATCH_SIZE - + (addr & W1_F0D_SCRATCH_MASK); + + if (copy > len) + copy = len; + + memcpy(&tmp[addr & W1_F0D_SCRATCH_MASK], buf, copy); + if (w1_f0d_write(sl, addr & ~W1_F0D_SCRATCH_MASK, + W1_F0D_SCRATCH_SIZE, tmp) < 0) { + count = -EIO; + goto out_up; + } + } else { + + copy = W1_F0D_SCRATCH_SIZE; + if (w1_f0d_write(sl, addr, copy, buf) < 0) { + count = -EIO; + goto out_up; + } + } + buf += copy; + addr += copy; + len -= copy; + } + +out_up: + mutex_unlock(&sl->master->mutex); + + return count; +} + +static struct bin_attribute w1_f0d_bin_attr = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = W1_F0D_EEPROM_SIZE, + .read = w1_f0d_read_bin, + .write = w1_f0d_write_bin, +}; + +static int w1_f0d_add_slave(struct w1_slave *sl) +{ + return sysfs_create_bin_file(&sl->dev.kobj, &w1_f0d_bin_attr); +} + +static void w1_f0d_remove_slave(struct w1_slave *sl) +{ + sysfs_remove_bin_file(&sl->dev.kobj, &w1_f0d_bin_attr); +} + +static struct w1_family_ops w1_f0d_fops = { + .add_slave = w1_f0d_add_slave, + .remove_slave = w1_f0d_remove_slave, +}; + +static struct w1_family w1_family_2d = { + .fid = W1_EEPROM_DS2805, + .fops = &w1_f0d_fops, +}; + +static int __init w1_f0d_init(void) +{ + pr_info("%s()\n", __func__); + return w1_register_family(&w1_family_2d); +} + +static void __exit w1_f0d_fini(void) +{ + pr_info("%s()\n", __func__); + w1_unregister_family(&w1_family_2d); +} + +module_init(w1_f0d_init); +module_exit(w1_f0d_fini); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andrew Worsley amworsley@gmail.com"); +MODULE_DESCRIPTION("w1 family 0d driver for DS2805, 1kb EEPROM"); diff --git a/drivers/w1/slaves/w1_ds28e04.c b/drivers/w1/slaves/w1_ds28e04.c new file mode 100644 index 000000000..e5eb19a34 --- /dev/null +++ b/drivers/w1/slaves/w1_ds28e04.c @@ -0,0 +1,416 @@ +/* + * w1_ds28e04.c - w1 family 1C (DS28E04) driver + * + * Copyright (c) 2012 Markus Franke <franke.m@sebakmt.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 <linux/crc16.h> +#include <linux/uaccess.h> + +#define CRC16_INIT 0 +#define CRC16_VALID 0xb001 + +#include <linux/w1.h> + +#define W1_FAMILY_DS28E04 0x1C + +/* Allow the strong pullup to be disabled, but default to enabled. + * If it was disabled a parasite powered device might not get the required + * current to copy the data from the scratchpad to EEPROM. If it is enabled + * parasite powered devices have a better chance of getting the current + * required. + */ +static int w1_strong_pullup = 1; +module_param_named(strong_pullup, w1_strong_pullup, int, 0); + +/* enable/disable CRC checking on DS28E04-100 memory accesses */ +static bool w1_enable_crccheck = true; + +#define W1_EEPROM_SIZE 512 +#define W1_PAGE_COUNT 16 +#define W1_PAGE_SIZE 32 +#define W1_PAGE_BITS 5 +#define W1_PAGE_MASK 0x1F + +#define W1_F1C_READ_EEPROM 0xF0 +#define W1_F1C_WRITE_SCRATCH 0x0F +#define W1_F1C_READ_SCRATCH 0xAA +#define W1_F1C_COPY_SCRATCH 0x55 +#define W1_F1C_ACCESS_WRITE 0x5A + +#define W1_1C_REG_LOGIC_STATE 0x220 + +struct w1_f1C_data { + u8 memory[W1_EEPROM_SIZE]; + u32 validcrc; +}; + +/** + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f1C_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return size - off; + + return count; +} + +static int w1_f1C_refresh_block(struct w1_slave *sl, struct w1_f1C_data *data, + int block) +{ + u8 wrbuf[3]; + int off = block * W1_PAGE_SIZE; + + if (data->validcrc & (1 << block)) + return 0; + + if (w1_reset_select_slave(sl)) { + data->validcrc = 0; + return -EIO; + } + + wrbuf[0] = W1_F1C_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, &data->memory[off], W1_PAGE_SIZE); + + /* cache the block if the CRC is valid */ + if (crc16(CRC16_INIT, &data->memory[off], W1_PAGE_SIZE) == CRC16_VALID) + data->validcrc |= (1 << block); + + return 0; +} + +static int w1_f1C_read(struct w1_slave *sl, int addr, int len, char *data) +{ + u8 wrbuf[3]; + + /* read directly from the EEPROM */ + if (w1_reset_select_slave(sl)) + return -EIO; + + wrbuf[0] = W1_F1C_READ_EEPROM; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, sizeof(wrbuf)); + return w1_read_block(sl->master, data, len); +} + +static ssize_t eeprom_read(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); + struct w1_f1C_data *data = sl->family_data; + int i, min_page, max_page; + + count = w1_f1C_fix_count(off, count, W1_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->mutex); + + if (w1_enable_crccheck) { + min_page = (off >> W1_PAGE_BITS); + max_page = (off + count - 1) >> W1_PAGE_BITS; + for (i = min_page; i <= max_page; i++) { + if (w1_f1C_refresh_block(sl, data, i)) { + count = -EIO; + goto out_up; + } + } + memcpy(buf, &data->memory[off], count); + } else { + count = w1_f1C_read(sl, off, count, buf); + } + +out_up: + mutex_unlock(&sl->master->mutex); + + return count; +} + +/** + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be on one page. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_PAGE_SIZE - (addr & W1_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f1C_write(struct w1_slave *sl, int addr, int len, const u8 *data) +{ + u8 wrbuf[4]; + u8 rdbuf[W1_PAGE_SIZE + 3]; + u8 es = (addr + len - 1) & 0x1f; + unsigned int tm = 10; + int i; + struct w1_f1C_data *f1C = sl->family_data; + + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F1C_WRITE_SCRATCH; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, 3); + w1_write_block(sl->master, data, len); + + /* Read the scratchpad and verify */ + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_8(sl->master, W1_F1C_READ_SCRATCH); + w1_read_block(sl->master, rdbuf, len + 3); + + /* Compare what was read against the data written */ + if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) || + (rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0)) + return -1; + + /* Copy the scratchpad to EEPROM */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F1C_COPY_SCRATCH; + wrbuf[3] = es; + + for (i = 0; i < sizeof(wrbuf); ++i) { + /* issue 10ms strong pullup (or delay) on the last byte + for writing the data from the scratchpad to EEPROM */ + if (w1_strong_pullup && i == sizeof(wrbuf)-1) + w1_next_pullup(sl->master, tm); + + w1_write_8(sl->master, wrbuf[i]); + } + + if (!w1_strong_pullup) + msleep(tm); + + if (w1_enable_crccheck) { + /* invalidate cached data */ + f1C->validcrc &= ~(1 << (addr >> W1_PAGE_BITS)); + } + + /* Reset the bus to wake up the EEPROM (this may not be needed) */ + w1_reset_bus(sl->master); + + return 0; +} + +static ssize_t eeprom_write(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); + int addr, len, idx; + + count = w1_f1C_fix_count(off, count, W1_EEPROM_SIZE); + if (count == 0) + return 0; + + if (w1_enable_crccheck) { + /* can only write full blocks in cached mode */ + if ((off & W1_PAGE_MASK) || (count & W1_PAGE_MASK)) { + dev_err(&sl->dev, "invalid offset/count off=%d cnt=%zd\n", + (int)off, count); + return -EINVAL; + } + + /* make sure the block CRCs are valid */ + for (idx = 0; idx < count; idx += W1_PAGE_SIZE) { + if (crc16(CRC16_INIT, &buf[idx], W1_PAGE_SIZE) + != CRC16_VALID) { + dev_err(&sl->dev, "bad CRC at offset %d\n", + (int)off); + return -EINVAL; + } + } + } + + mutex_lock(&sl->master->mutex); + + /* Can only write data to one page at a time */ + idx = 0; + while (idx < count) { + addr = off + idx; + len = W1_PAGE_SIZE - (addr & W1_PAGE_MASK); + if (len > (count - idx)) + len = count - idx; + + if (w1_f1C_write(sl, addr, len, &buf[idx]) < 0) { + count = -EIO; + goto out_up; + } + idx += len; + } + +out_up: + mutex_unlock(&sl->master->mutex); + + return count; +} + +static BIN_ATTR_RW(eeprom, W1_EEPROM_SIZE); + +static ssize_t pio_read(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); + int ret; + + /* check arguments */ + if (off != 0 || count != 1 || buf == NULL) + return -EINVAL; + + mutex_lock(&sl->master->mutex); + ret = w1_f1C_read(sl, W1_1C_REG_LOGIC_STATE, count, buf); + mutex_unlock(&sl->master->mutex); + + return ret; +} + +static ssize_t pio_write(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 wrbuf[3]; + u8 ack; + + /* check arguments */ + if (off != 0 || count != 1 || buf == NULL) + return -EINVAL; + + mutex_lock(&sl->master->mutex); + + /* Write the PIO data */ + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->mutex); + return -1; + } + + /* set bit 7..2 to value '1' */ + *buf = *buf | 0xFC; + + wrbuf[0] = W1_F1C_ACCESS_WRITE; + wrbuf[1] = *buf; + wrbuf[2] = ~(*buf); + w1_write_block(sl->master, wrbuf, 3); + + w1_read_block(sl->master, &ack, sizeof(ack)); + + mutex_unlock(&sl->master->mutex); + + /* check for acknowledgement */ + if (ack != 0xAA) + return -EIO; + + return count; +} + +static BIN_ATTR_RW(pio, 1); + +static ssize_t crccheck_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%d\n", w1_enable_crccheck); +} + +static ssize_t crccheck_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int err = kstrtobool(buf, &w1_enable_crccheck); + + if (err) + return err; + + return count; +} + +static DEVICE_ATTR_RW(crccheck); + +static struct attribute *w1_f1C_attrs[] = { + &dev_attr_crccheck.attr, + NULL, +}; + +static struct bin_attribute *w1_f1C_bin_attrs[] = { + &bin_attr_eeprom, + &bin_attr_pio, + NULL, +}; + +static const struct attribute_group w1_f1C_group = { + .attrs = w1_f1C_attrs, + .bin_attrs = w1_f1C_bin_attrs, +}; + +static const struct attribute_group *w1_f1C_groups[] = { + &w1_f1C_group, + NULL, +}; + +static int w1_f1C_add_slave(struct w1_slave *sl) +{ + struct w1_f1C_data *data = NULL; + + if (w1_enable_crccheck) { + data = kzalloc(sizeof(struct w1_f1C_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + sl->family_data = data; + } + + return 0; +} + +static void w1_f1C_remove_slave(struct w1_slave *sl) +{ + kfree(sl->family_data); + sl->family_data = NULL; +} + +static struct w1_family_ops w1_f1C_fops = { + .add_slave = w1_f1C_add_slave, + .remove_slave = w1_f1C_remove_slave, + .groups = w1_f1C_groups, +}; + +static struct w1_family w1_family_1C = { + .fid = W1_FAMILY_DS28E04, + .fops = &w1_f1C_fops, +}; +module_w1_family(w1_family_1C); + +MODULE_AUTHOR("Markus Franke <franke.m@sebakmt.com>, <franm@hrz.tu-chemnitz.de>"); +MODULE_DESCRIPTION("w1 family 1C driver for DS28E04, 4kb EEPROM and PIO"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS28E04)); diff --git a/drivers/w1/slaves/w1_ds28e17.c b/drivers/w1/slaves/w1_ds28e17.c new file mode 100644 index 000000000..e78b63ea4 --- /dev/null +++ b/drivers/w1/slaves/w1_ds28e17.c @@ -0,0 +1,771 @@ +/* + * w1_ds28e17.c - w1 family 19 (DS28E17) driver + * + * Copyright (c) 2016 Jan Kandziora <jjj@gmx.de> + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include <linux/crc16.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/uaccess.h> + +#define CRC16_INIT 0 + +#include <linux/w1.h> + +#define W1_FAMILY_DS28E17 0x19 + +/* Module setup. */ +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jan Kandziora <jjj@gmx.de>"); +MODULE_DESCRIPTION("w1 family 19 driver for DS28E17, 1-wire to I2C master bridge"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS28E17)); + + +/* Default I2C speed to be set when a DS28E17 is detected. */ +static int i2c_speed = 100; +module_param_named(speed, i2c_speed, int, (S_IRUSR | S_IWUSR)); +MODULE_PARM_DESC(speed, "Default I2C speed to be set when a DS28E17 is detected"); + +/* Default I2C stretch value to be set when a DS28E17 is detected. */ +static char i2c_stretch = 1; +module_param_named(stretch, i2c_stretch, byte, (S_IRUSR | S_IWUSR)); +MODULE_PARM_DESC(stretch, "Default I2C stretch value to be set when a DS28E17 is detected"); + +/* DS28E17 device command codes. */ +#define W1_F19_WRITE_DATA_WITH_STOP 0x4B +#define W1_F19_WRITE_DATA_NO_STOP 0x5A +#define W1_F19_WRITE_DATA_ONLY 0x69 +#define W1_F19_WRITE_DATA_ONLY_WITH_STOP 0x78 +#define W1_F19_READ_DATA_WITH_STOP 0x87 +#define W1_F19_WRITE_READ_DATA_WITH_STOP 0x2D +#define W1_F19_WRITE_CONFIGURATION 0xD2 +#define W1_F19_READ_CONFIGURATION 0xE1 +#define W1_F19_ENABLE_SLEEP_MODE 0x1E +#define W1_F19_READ_DEVICE_REVISION 0xC4 + +/* DS28E17 status bits */ +#define W1_F19_STATUS_CRC 0x01 +#define W1_F19_STATUS_ADDRESS 0x02 +#define W1_F19_STATUS_START 0x08 + +/* + * Maximum number of I2C bytes to transfer within one CRC16 protected onewire + * command. + * */ +#define W1_F19_WRITE_DATA_LIMIT 255 + +/* Maximum number of I2C bytes to read with one onewire command. */ +#define W1_F19_READ_DATA_LIMIT 255 + +/* Constants for calculating the busy sleep. */ +#define W1_F19_BUSY_TIMEBASES { 90, 23, 10 } +#define W1_F19_BUSY_GRATUITY 1000 + +/* Number of checks for the busy flag before timeout. */ +#define W1_F19_BUSY_CHECKS 1000 + + +/* Slave specific data. */ +struct w1_f19_data { + u8 speed; + u8 stretch; + struct i2c_adapter adapter; +}; + + +/* Wait a while until the busy flag clears. */ +static int w1_f19_i2c_busy_wait(struct w1_slave *sl, size_t count) +{ + const unsigned long timebases[3] = W1_F19_BUSY_TIMEBASES; + struct w1_f19_data *data = sl->family_data; + unsigned int checks; + + /* Check the busy flag first in any case.*/ + if (w1_touch_bit(sl->master, 1) == 0) + return 0; + + /* + * Do a generously long sleep in the beginning, + * as we have to wait at least this time for all + * the I2C bytes at the given speed to be transferred. + */ + usleep_range(timebases[data->speed] * (data->stretch) * count, + timebases[data->speed] * (data->stretch) * count + + W1_F19_BUSY_GRATUITY); + + /* Now continusly check the busy flag sent by the DS28E17. */ + checks = W1_F19_BUSY_CHECKS; + while ((checks--) > 0) { + /* Return success if the busy flag is cleared. */ + if (w1_touch_bit(sl->master, 1) == 0) + return 0; + + /* Wait one non-streched byte timeslot. */ + udelay(timebases[data->speed]); + } + + /* Timeout. */ + dev_warn(&sl->dev, "busy timeout\n"); + return -ETIMEDOUT; +} + + +/* Utility function: result. */ +static size_t w1_f19_error(struct w1_slave *sl, u8 w1_buf[]) +{ + /* Warnings. */ + if (w1_buf[0] & W1_F19_STATUS_CRC) + dev_warn(&sl->dev, "crc16 mismatch\n"); + if (w1_buf[0] & W1_F19_STATUS_ADDRESS) + dev_warn(&sl->dev, "i2c device not responding\n"); + if ((w1_buf[0] & (W1_F19_STATUS_CRC | W1_F19_STATUS_ADDRESS)) == 0 + && w1_buf[1] != 0) { + dev_warn(&sl->dev, "i2c short write, %d bytes not acknowledged\n", + w1_buf[1]); + } + + /* Check error conditions. */ + if (w1_buf[0] & W1_F19_STATUS_ADDRESS) + return -ENXIO; + if (w1_buf[0] & W1_F19_STATUS_START) + return -EAGAIN; + if (w1_buf[0] != 0 || w1_buf[1] != 0) + return -EIO; + + /* All ok. */ + return 0; +} + + +/* Utility function: write data to I2C slave, single chunk. */ +static int __w1_f19_i2c_write(struct w1_slave *sl, + const u8 *command, size_t command_count, + const u8 *buffer, size_t count) +{ + u16 crc; + int error; + u8 w1_buf[2]; + + /* Send command and I2C data to DS28E17. */ + crc = crc16(CRC16_INIT, command, command_count); + w1_write_block(sl->master, command, command_count); + + w1_buf[0] = count; + crc = crc16(crc, w1_buf, 1); + w1_write_8(sl->master, w1_buf[0]); + + crc = crc16(crc, buffer, count); + w1_write_block(sl->master, buffer, count); + + w1_buf[0] = ~(crc & 0xFF); + w1_buf[1] = ~((crc >> 8) & 0xFF); + w1_write_block(sl->master, w1_buf, 2); + + /* Wait until busy flag clears (or timeout). */ + if (w1_f19_i2c_busy_wait(sl, count + 1) < 0) + return -ETIMEDOUT; + + /* Read status from DS28E17. */ + w1_read_block(sl->master, w1_buf, 2); + + /* Check error conditions. */ + error = w1_f19_error(sl, w1_buf); + if (error < 0) + return error; + + /* Return number of bytes written. */ + return count; +} + + +/* Write data to I2C slave. */ +static int w1_f19_i2c_write(struct w1_slave *sl, u16 i2c_address, + const u8 *buffer, size_t count, bool stop) +{ + int result; + int remaining = count; + const u8 *p; + u8 command[2]; + + /* Check input. */ + if (count == 0) + return -EOPNOTSUPP; + + /* Check whether we need multiple commands. */ + if (count <= W1_F19_WRITE_DATA_LIMIT) { + /* + * Small data amount. Data can be sent with + * a single onewire command. + */ + + /* Send all data to DS28E17. */ + command[0] = (stop ? W1_F19_WRITE_DATA_WITH_STOP + : W1_F19_WRITE_DATA_NO_STOP); + command[1] = i2c_address << 1; + result = __w1_f19_i2c_write(sl, command, 2, buffer, count); + } else { + /* Large data amount. Data has to be sent in multiple chunks. */ + + /* Send first chunk to DS28E17. */ + p = buffer; + command[0] = W1_F19_WRITE_DATA_NO_STOP; + command[1] = i2c_address << 1; + result = __w1_f19_i2c_write(sl, command, 2, p, + W1_F19_WRITE_DATA_LIMIT); + if (result < 0) + return result; + + /* Resume to same DS28E17. */ + if (w1_reset_resume_command(sl->master)) + return -EIO; + + /* Next data chunk. */ + p += W1_F19_WRITE_DATA_LIMIT; + remaining -= W1_F19_WRITE_DATA_LIMIT; + + while (remaining > W1_F19_WRITE_DATA_LIMIT) { + /* Send intermediate chunk to DS28E17. */ + command[0] = W1_F19_WRITE_DATA_ONLY; + result = __w1_f19_i2c_write(sl, command, 1, p, + W1_F19_WRITE_DATA_LIMIT); + if (result < 0) + return result; + + /* Resume to same DS28E17. */ + if (w1_reset_resume_command(sl->master)) + return -EIO; + + /* Next data chunk. */ + p += W1_F19_WRITE_DATA_LIMIT; + remaining -= W1_F19_WRITE_DATA_LIMIT; + } + + /* Send final chunk to DS28E17. */ + command[0] = (stop ? W1_F19_WRITE_DATA_ONLY_WITH_STOP + : W1_F19_WRITE_DATA_ONLY); + result = __w1_f19_i2c_write(sl, command, 1, p, remaining); + } + + return result; +} + + +/* Read data from I2C slave. */ +static int w1_f19_i2c_read(struct w1_slave *sl, u16 i2c_address, + u8 *buffer, size_t count) +{ + u16 crc; + int error; + u8 w1_buf[5]; + + /* Check input. */ + if (count == 0) + return -EOPNOTSUPP; + + /* Send command to DS28E17. */ + w1_buf[0] = W1_F19_READ_DATA_WITH_STOP; + w1_buf[1] = i2c_address << 1 | 0x01; + w1_buf[2] = count; + crc = crc16(CRC16_INIT, w1_buf, 3); + w1_buf[3] = ~(crc & 0xFF); + w1_buf[4] = ~((crc >> 8) & 0xFF); + w1_write_block(sl->master, w1_buf, 5); + + /* Wait until busy flag clears (or timeout). */ + if (w1_f19_i2c_busy_wait(sl, count + 1) < 0) + return -ETIMEDOUT; + + /* Read status from DS28E17. */ + w1_buf[0] = w1_read_8(sl->master); + w1_buf[1] = 0; + + /* Check error conditions. */ + error = w1_f19_error(sl, w1_buf); + if (error < 0) + return error; + + /* Read received I2C data from DS28E17. */ + return w1_read_block(sl->master, buffer, count); +} + + +/* Write to, then read data from I2C slave. */ +static int w1_f19_i2c_write_read(struct w1_slave *sl, u16 i2c_address, + const u8 *wbuffer, size_t wcount, u8 *rbuffer, size_t rcount) +{ + u16 crc; + int error; + u8 w1_buf[3]; + + /* Check input. */ + if (wcount == 0 || rcount == 0) + return -EOPNOTSUPP; + + /* Send command and I2C data to DS28E17. */ + w1_buf[0] = W1_F19_WRITE_READ_DATA_WITH_STOP; + w1_buf[1] = i2c_address << 1; + w1_buf[2] = wcount; + crc = crc16(CRC16_INIT, w1_buf, 3); + w1_write_block(sl->master, w1_buf, 3); + + crc = crc16(crc, wbuffer, wcount); + w1_write_block(sl->master, wbuffer, wcount); + + w1_buf[0] = rcount; + crc = crc16(crc, w1_buf, 1); + w1_buf[1] = ~(crc & 0xFF); + w1_buf[2] = ~((crc >> 8) & 0xFF); + w1_write_block(sl->master, w1_buf, 3); + + /* Wait until busy flag clears (or timeout). */ + if (w1_f19_i2c_busy_wait(sl, wcount + rcount + 2) < 0) + return -ETIMEDOUT; + + /* Read status from DS28E17. */ + w1_read_block(sl->master, w1_buf, 2); + + /* Check error conditions. */ + error = w1_f19_error(sl, w1_buf); + if (error < 0) + return error; + + /* Read received I2C data from DS28E17. */ + return w1_read_block(sl->master, rbuffer, rcount); +} + + +/* Do an I2C master transfer. */ +static int w1_f19_i2c_master_transfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + struct w1_slave *sl = (struct w1_slave *) adapter->algo_data; + int i = 0; + int result = 0; + + /* Start onewire transaction. */ + mutex_lock(&sl->master->bus_mutex); + + /* Select DS28E17. */ + if (w1_reset_select_slave(sl)) { + i = -EIO; + goto error; + } + + /* Loop while there are still messages to transfer. */ + while (i < num) { + /* + * Check for special case: Small write followed + * by read to same I2C device. + */ + if (i < (num-1) + && msgs[i].addr == msgs[i+1].addr + && !(msgs[i].flags & I2C_M_RD) + && (msgs[i+1].flags & I2C_M_RD) + && (msgs[i].len <= W1_F19_WRITE_DATA_LIMIT)) { + /* + * The DS28E17 has a combined transfer + * for small write+read. + */ + result = w1_f19_i2c_write_read(sl, msgs[i].addr, + msgs[i].buf, msgs[i].len, + msgs[i+1].buf, msgs[i+1].len); + if (result < 0) { + i = result; + goto error; + } + + /* + * Check if we should interpret the read data + * as a length byte. The DS28E17 unfortunately + * has no read without stop, so we can just do + * another simple read in that case. + */ + if (msgs[i+1].flags & I2C_M_RECV_LEN) { + result = w1_f19_i2c_read(sl, msgs[i+1].addr, + &(msgs[i+1].buf[1]), msgs[i+1].buf[0]); + if (result < 0) { + i = result; + goto error; + } + } + + /* Eat up read message, too. */ + i++; + } else if (msgs[i].flags & I2C_M_RD) { + /* Read transfer. */ + result = w1_f19_i2c_read(sl, msgs[i].addr, + msgs[i].buf, msgs[i].len); + if (result < 0) { + i = result; + goto error; + } + + /* + * Check if we should interpret the read data + * as a length byte. The DS28E17 unfortunately + * has no read without stop, so we can just do + * another simple read in that case. + */ + if (msgs[i].flags & I2C_M_RECV_LEN) { + result = w1_f19_i2c_read(sl, + msgs[i].addr, + &(msgs[i].buf[1]), + msgs[i].buf[0]); + if (result < 0) { + i = result; + goto error; + } + } + } else { + /* + * Write transfer. + * Stop condition only for last + * transfer. + */ + result = w1_f19_i2c_write(sl, + msgs[i].addr, + msgs[i].buf, + msgs[i].len, + i == (num-1)); + if (result < 0) { + i = result; + goto error; + } + } + + /* Next message. */ + i++; + + /* Are there still messages to send/receive? */ + if (i < num) { + /* Yes. Resume to same DS28E17. */ + if (w1_reset_resume_command(sl->master)) { + i = -EIO; + goto error; + } + } + } + +error: + /* End onewire transaction. */ + mutex_unlock(&sl->master->bus_mutex); + + /* Return number of messages processed or error. */ + return i; +} + + +/* Get I2C adapter functionality. */ +static u32 w1_f19_i2c_functionality(struct i2c_adapter *adapter) +{ + /* + * Plain I2C functions only. + * SMBus is emulated by the kernel's I2C layer. + * No "I2C_FUNC_SMBUS_QUICK" + * No "I2C_FUNC_SMBUS_READ_BLOCK_DATA" + * No "I2C_FUNC_SMBUS_BLOCK_PROC_CALL" + */ + return I2C_FUNC_I2C | + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_PROC_CALL | + I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK | + I2C_FUNC_SMBUS_PEC; +} + + +/* I2C adapter quirks. */ +static const struct i2c_adapter_quirks w1_f19_i2c_adapter_quirks = { + .max_read_len = W1_F19_READ_DATA_LIMIT, +}; + +/* I2C algorithm. */ +static const struct i2c_algorithm w1_f19_i2c_algorithm = { + .master_xfer = w1_f19_i2c_master_transfer, + .functionality = w1_f19_i2c_functionality, +}; + + +/* Read I2C speed from DS28E17. */ +static int w1_f19_get_i2c_speed(struct w1_slave *sl) +{ + struct w1_f19_data *data = sl->family_data; + int result = -EIO; + + /* Start onewire transaction. */ + mutex_lock(&sl->master->bus_mutex); + + /* Select slave. */ + if (w1_reset_select_slave(sl)) + goto error; + + /* Read slave configuration byte. */ + w1_write_8(sl->master, W1_F19_READ_CONFIGURATION); + result = w1_read_8(sl->master); + if (result < 0 || result > 2) { + result = -EIO; + goto error; + } + + /* Update speed in slave specific data. */ + data->speed = result; + +error: + /* End onewire transaction. */ + mutex_unlock(&sl->master->bus_mutex); + + return result; +} + + +/* Set I2C speed on DS28E17. */ +static int __w1_f19_set_i2c_speed(struct w1_slave *sl, u8 speed) +{ + struct w1_f19_data *data = sl->family_data; + const int i2c_speeds[3] = { 100, 400, 900 }; + u8 w1_buf[2]; + + /* Select slave. */ + if (w1_reset_select_slave(sl)) + return -EIO; + + w1_buf[0] = W1_F19_WRITE_CONFIGURATION; + w1_buf[1] = speed; + w1_write_block(sl->master, w1_buf, 2); + + /* Update speed in slave specific data. */ + data->speed = speed; + + dev_info(&sl->dev, "i2c speed set to %d kBaud\n", i2c_speeds[speed]); + + return 0; +} + +static int w1_f19_set_i2c_speed(struct w1_slave *sl, u8 speed) +{ + int result; + + /* Start onewire transaction. */ + mutex_lock(&sl->master->bus_mutex); + + /* Set I2C speed on DS28E17. */ + result = __w1_f19_set_i2c_speed(sl, speed); + + /* End onewire transaction. */ + mutex_unlock(&sl->master->bus_mutex); + + return result; +} + + +/* Sysfs attributes. */ + +/* I2C speed attribute for a single chip. */ +static ssize_t speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + int result; + + /* Read current speed from slave. Updates data->speed. */ + result = w1_f19_get_i2c_speed(sl); + if (result < 0) + return result; + + /* Return current speed value. */ + return sprintf(buf, "%d\n", result); +} + +static ssize_t speed_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + int error; + + /* Valid values are: "100", "400", "900" */ + if (count < 3 || count > 4 || !buf) + return -EINVAL; + if (count == 4 && buf[3] != '\n') + return -EINVAL; + if (buf[1] != '0' || buf[2] != '0') + return -EINVAL; + + /* Set speed on slave. */ + switch (buf[0]) { + case '1': + error = w1_f19_set_i2c_speed(sl, 0); + break; + case '4': + error = w1_f19_set_i2c_speed(sl, 1); + break; + case '9': + error = w1_f19_set_i2c_speed(sl, 2); + break; + default: + return -EINVAL; + } + + if (error < 0) + return error; + + /* Return bytes written. */ + return count; +} + +static DEVICE_ATTR_RW(speed); + + +/* Busy stretch attribute for a single chip. */ +static ssize_t stretch_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + struct w1_f19_data *data = sl->family_data; + + /* Return current stretch value. */ + return sprintf(buf, "%d\n", data->stretch); +} + +static ssize_t stretch_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + struct w1_f19_data *data = sl->family_data; + + /* Valid values are '1' to '9' */ + if (count < 1 || count > 2 || !buf) + return -EINVAL; + if (count == 2 && buf[1] != '\n') + return -EINVAL; + if (buf[0] < '1' || buf[0] > '9') + return -EINVAL; + + /* Set busy stretch value. */ + data->stretch = buf[0] & 0x0F; + + /* Return bytes written. */ + return count; +} + +static DEVICE_ATTR_RW(stretch); + + +/* All attributes. */ +static struct attribute *w1_f19_attrs[] = { + &dev_attr_speed.attr, + &dev_attr_stretch.attr, + NULL, +}; + +static const struct attribute_group w1_f19_group = { + .attrs = w1_f19_attrs, +}; + +static const struct attribute_group *w1_f19_groups[] = { + &w1_f19_group, + NULL, +}; + + +/* Slave add and remove functions. */ +static int w1_f19_add_slave(struct w1_slave *sl) +{ + struct w1_f19_data *data = NULL; + + /* Allocate memory for slave specific data. */ + data = devm_kzalloc(&sl->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + sl->family_data = data; + + /* Setup default I2C speed on slave. */ + switch (i2c_speed) { + case 100: + __w1_f19_set_i2c_speed(sl, 0); + break; + case 400: + __w1_f19_set_i2c_speed(sl, 1); + break; + case 900: + __w1_f19_set_i2c_speed(sl, 2); + break; + default: + /* + * A i2c_speed module parameter of anything else + * than 100, 400, 900 means not to touch the + * speed of the DS28E17. + * We assume 400kBaud, the power-on value. + */ + data->speed = 1; + } + + /* + * Setup default busy stretch + * configuration for the DS28E17. + */ + data->stretch = i2c_stretch; + + /* Setup I2C adapter. */ + data->adapter.owner = THIS_MODULE; + data->adapter.algo = &w1_f19_i2c_algorithm; + data->adapter.algo_data = sl; + strcpy(data->adapter.name, "w1-"); + strcat(data->adapter.name, sl->name); + data->adapter.dev.parent = &sl->dev; + data->adapter.quirks = &w1_f19_i2c_adapter_quirks; + + return i2c_add_adapter(&data->adapter); +} + +static void w1_f19_remove_slave(struct w1_slave *sl) +{ + struct w1_f19_data *family_data = sl->family_data; + + /* Delete I2C adapter. */ + i2c_del_adapter(&family_data->adapter); + + /* Free slave specific data. */ + devm_kfree(&sl->dev, family_data); + sl->family_data = NULL; +} + + +/* Declarations within the w1 subsystem. */ +static struct w1_family_ops w1_f19_fops = { + .add_slave = w1_f19_add_slave, + .remove_slave = w1_f19_remove_slave, + .groups = w1_f19_groups, +}; + +static struct w1_family w1_family_19 = { + .fid = W1_FAMILY_DS28E17, + .fops = &w1_f19_fops, +}; + + +/* Module init and remove functions. */ +static int __init w1_f19_init(void) +{ + return w1_register_family(&w1_family_19); +} + +static void __exit w1_f19_fini(void) +{ + w1_unregister_family(&w1_family_19); +} + +module_init(w1_f19_init); +module_exit(w1_f19_fini); + diff --git a/drivers/w1/slaves/w1_smem.c b/drivers/w1/slaves/w1_smem.c new file mode 100644 index 000000000..e556b0caf --- /dev/null +++ b/drivers/w1/slaves/w1_smem.c @@ -0,0 +1,73 @@ +/* + * w1_smem.c + * + * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the smems of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <asm/types.h> + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> + +#include <linux/w1.h> + +#define W1_FAMILY_SMEM_01 0x01 +#define W1_FAMILY_SMEM_81 0x81 + +static struct w1_family w1_smem_family_01 = { + .fid = W1_FAMILY_SMEM_01, +}; + +static struct w1_family w1_smem_family_81 = { + .fid = W1_FAMILY_SMEM_81, +}; + +static int __init w1_smem_init(void) +{ + int err; + + err = w1_register_family(&w1_smem_family_01); + if (err) + return err; + + err = w1_register_family(&w1_smem_family_81); + if (err) { + w1_unregister_family(&w1_smem_family_01); + return err; + } + + return 0; +} + +static void __exit w1_smem_fini(void) +{ + w1_unregister_family(&w1_smem_family_01); + w1_unregister_family(&w1_smem_family_81); +} + +module_init(w1_smem_init); +module_exit(w1_smem_fini); + +MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>"); +MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol, 64bit memory family."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_SMEM_01)); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_SMEM_81)); diff --git a/drivers/w1/slaves/w1_therm.c b/drivers/w1/slaves/w1_therm.c new file mode 100644 index 000000000..aba727294 --- /dev/null +++ b/drivers/w1/slaves/w1_therm.c @@ -0,0 +1,765 @@ +/* + * w1_therm.c + * + * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the therms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <asm/types.h> + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/sched.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/hwmon.h> + +#include <linux/w1.h> + +#define W1_THERM_DS18S20 0x10 +#define W1_THERM_DS1822 0x22 +#define W1_THERM_DS18B20 0x28 +#define W1_THERM_DS1825 0x3B +#define W1_THERM_DS28EA00 0x42 + +/* Allow the strong pullup to be disabled, but default to enabled. + * If it was disabled a parasite powered device might not get the require + * current to do a temperature conversion. If it is enabled parasite powered + * devices have a better chance of getting the current required. + * In case the parasite power-detection is not working (seems to be the case + * for some DS18S20) the strong pullup can also be forced, regardless of the + * power state of the devices. + * + * Summary of options: + * - strong_pullup = 0 Disable strong pullup completely + * - strong_pullup = 1 Enable automatic strong pullup detection + * - strong_pullup = 2 Force strong pullup + */ +static int w1_strong_pullup = 1; +module_param_named(strong_pullup, w1_strong_pullup, int, 0); + +struct w1_therm_family_data { + uint8_t rom[9]; + atomic_t refcnt; +}; + +struct therm_info { + u8 rom[9]; + u8 crc; + u8 verdict; +}; + +/* return the address of the refcnt in the family data */ +#define THERM_REFCNT(family_data) \ + (&((struct w1_therm_family_data *)family_data)->refcnt) + +static int w1_therm_add_slave(struct w1_slave *sl) +{ + sl->family_data = kzalloc(sizeof(struct w1_therm_family_data), + GFP_KERNEL); + if (!sl->family_data) + return -ENOMEM; + atomic_set(THERM_REFCNT(sl->family_data), 1); + return 0; +} + +static void w1_therm_remove_slave(struct w1_slave *sl) +{ + int refcnt = atomic_sub_return(1, THERM_REFCNT(sl->family_data)); + + while (refcnt) { + msleep(1000); + refcnt = atomic_read(THERM_REFCNT(sl->family_data)); + } + kfree(sl->family_data); + sl->family_data = NULL; +} + +static ssize_t w1_slave_show(struct device *device, + struct device_attribute *attr, char *buf); + +static ssize_t w1_slave_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size); + +static ssize_t w1_seq_show(struct device *device, + struct device_attribute *attr, char *buf); + +static DEVICE_ATTR_RW(w1_slave); +static DEVICE_ATTR_RO(w1_seq); + +static struct attribute *w1_therm_attrs[] = { + &dev_attr_w1_slave.attr, + NULL, +}; + +static struct attribute *w1_ds28ea00_attrs[] = { + &dev_attr_w1_slave.attr, + &dev_attr_w1_seq.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(w1_therm); +ATTRIBUTE_GROUPS(w1_ds28ea00); + +#if IS_REACHABLE(CONFIG_HWMON) +static int w1_read_temp(struct device *dev, u32 attr, int channel, + long *val); + +static umode_t w1_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return attr == hwmon_temp_input ? 0444 : 0; +} + +static int w1_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_temp: + return w1_read_temp(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static const u32 w1_temp_config[] = { + HWMON_T_INPUT, + 0 +}; + +static const struct hwmon_channel_info w1_temp = { + .type = hwmon_temp, + .config = w1_temp_config, +}; + +static const struct hwmon_channel_info *w1_info[] = { + &w1_temp, + NULL +}; + +static const struct hwmon_ops w1_hwmon_ops = { + .is_visible = w1_is_visible, + .read = w1_read, +}; + +static const struct hwmon_chip_info w1_chip_info = { + .ops = &w1_hwmon_ops, + .info = w1_info, +}; +#define W1_CHIPINFO (&w1_chip_info) +#else +#define W1_CHIPINFO NULL +#endif + +static struct w1_family_ops w1_therm_fops = { + .add_slave = w1_therm_add_slave, + .remove_slave = w1_therm_remove_slave, + .groups = w1_therm_groups, + .chip_info = W1_CHIPINFO, +}; + +static struct w1_family_ops w1_ds28ea00_fops = { + .add_slave = w1_therm_add_slave, + .remove_slave = w1_therm_remove_slave, + .groups = w1_ds28ea00_groups, + .chip_info = W1_CHIPINFO, +}; + +static struct w1_family w1_therm_family_DS18S20 = { + .fid = W1_THERM_DS18S20, + .fops = &w1_therm_fops, +}; + +static struct w1_family w1_therm_family_DS18B20 = { + .fid = W1_THERM_DS18B20, + .fops = &w1_therm_fops, +}; + +static struct w1_family w1_therm_family_DS1822 = { + .fid = W1_THERM_DS1822, + .fops = &w1_therm_fops, +}; + +static struct w1_family w1_therm_family_DS28EA00 = { + .fid = W1_THERM_DS28EA00, + .fops = &w1_ds28ea00_fops, +}; + +static struct w1_family w1_therm_family_DS1825 = { + .fid = W1_THERM_DS1825, + .fops = &w1_therm_fops, +}; + +struct w1_therm_family_converter { + u8 broken; + u16 reserved; + struct w1_family *f; + int (*convert)(u8 rom[9]); + int (*precision)(struct device *device, int val); + int (*eeprom)(struct device *device); +}; + +/* write configuration to eeprom */ +static inline int w1_therm_eeprom(struct device *device); + +/* Set precision for conversion */ +static inline int w1_DS18B20_precision(struct device *device, int val); +static inline int w1_DS18S20_precision(struct device *device, int val); + +/* The return value is millidegrees Centigrade. */ +static inline int w1_DS18B20_convert_temp(u8 rom[9]); +static inline int w1_DS18S20_convert_temp(u8 rom[9]); + +static struct w1_therm_family_converter w1_therm_families[] = { + { + .f = &w1_therm_family_DS18S20, + .convert = w1_DS18S20_convert_temp, + .precision = w1_DS18S20_precision, + .eeprom = w1_therm_eeprom + }, + { + .f = &w1_therm_family_DS1822, + .convert = w1_DS18B20_convert_temp, + .precision = w1_DS18S20_precision, + .eeprom = w1_therm_eeprom + }, + { + .f = &w1_therm_family_DS18B20, + .convert = w1_DS18B20_convert_temp, + .precision = w1_DS18B20_precision, + .eeprom = w1_therm_eeprom + }, + { + .f = &w1_therm_family_DS28EA00, + .convert = w1_DS18B20_convert_temp, + .precision = w1_DS18S20_precision, + .eeprom = w1_therm_eeprom + }, + { + .f = &w1_therm_family_DS1825, + .convert = w1_DS18B20_convert_temp, + .precision = w1_DS18S20_precision, + .eeprom = w1_therm_eeprom + } +}; + +static inline int w1_therm_eeprom(struct device *device) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct w1_master *dev = sl->master; + u8 rom[9], external_power; + int ret, max_trying = 10; + u8 *family_data = sl->family_data; + + if (!sl->family_data) { + ret = -ENODEV; + goto error; + } + + /* prevent the slave from going away in sleep */ + atomic_inc(THERM_REFCNT(family_data)); + + ret = mutex_lock_interruptible(&dev->bus_mutex); + if (ret != 0) + goto dec_refcnt; + + memset(rom, 0, sizeof(rom)); + + while (max_trying--) { + if (!w1_reset_select_slave(sl)) { + unsigned int tm = 10; + unsigned long sleep_rem; + + /* check if in parasite mode */ + w1_write_8(dev, W1_READ_PSUPPLY); + external_power = w1_read_8(dev); + + if (w1_reset_select_slave(sl)) + continue; + + /* 10ms strong pullup/delay after the copy command */ + if (w1_strong_pullup == 2 || + (!external_power && w1_strong_pullup)) + w1_next_pullup(dev, tm); + + w1_write_8(dev, W1_COPY_SCRATCHPAD); + + if (external_power) { + mutex_unlock(&dev->bus_mutex); + + sleep_rem = msleep_interruptible(tm); + if (sleep_rem != 0) { + ret = -EINTR; + goto dec_refcnt; + } + + ret = mutex_lock_interruptible(&dev->bus_mutex); + if (ret != 0) + goto dec_refcnt; + } else if (!w1_strong_pullup) { + sleep_rem = msleep_interruptible(tm); + if (sleep_rem != 0) { + ret = -EINTR; + goto mt_unlock; + } + } + + break; + } + } + +mt_unlock: + mutex_unlock(&dev->bus_mutex); +dec_refcnt: + atomic_dec(THERM_REFCNT(family_data)); +error: + return ret; +} + +/* DS18S20 does not feature configuration register */ +static inline int w1_DS18S20_precision(struct device *device, int val) +{ + return 0; +} + +static inline int w1_DS18B20_precision(struct device *device, int val) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct w1_master *dev = sl->master; + u8 rom[9], crc; + int ret, max_trying = 10; + u8 *family_data = sl->family_data; + uint8_t precision_bits; + uint8_t mask = 0x60; + + if (val > 12 || val < 9) { + pr_warn("Unsupported precision\n"); + ret = -EINVAL; + goto error; + } + + if (!sl->family_data) { + ret = -ENODEV; + goto error; + } + + /* prevent the slave from going away in sleep */ + atomic_inc(THERM_REFCNT(family_data)); + + ret = mutex_lock_interruptible(&dev->bus_mutex); + if (ret != 0) + goto dec_refcnt; + + memset(rom, 0, sizeof(rom)); + + /* translate precision to bitmask (see datasheet page 9) */ + switch (val) { + case 9: + precision_bits = 0x00; + break; + case 10: + precision_bits = 0x20; + break; + case 11: + precision_bits = 0x40; + break; + case 12: + default: + precision_bits = 0x60; + break; + } + + while (max_trying--) { + crc = 0; + + if (!w1_reset_select_slave(sl)) { + int count = 0; + + /* read values to only alter precision bits */ + w1_write_8(dev, W1_READ_SCRATCHPAD); + count = w1_read_block(dev, rom, 9); + if (count != 9) + dev_warn(device, "w1_read_block() returned %u instead of 9.\n", count); + + crc = w1_calc_crc8(rom, 8); + if (rom[8] == crc) { + rom[4] = (rom[4] & ~mask) | (precision_bits & mask); + + if (!w1_reset_select_slave(sl)) { + w1_write_8(dev, W1_WRITE_SCRATCHPAD); + w1_write_8(dev, rom[2]); + w1_write_8(dev, rom[3]); + w1_write_8(dev, rom[4]); + + break; + } + } + } + } + + mutex_unlock(&dev->bus_mutex); +dec_refcnt: + atomic_dec(THERM_REFCNT(family_data)); +error: + return ret; +} + +static inline int w1_DS18B20_convert_temp(u8 rom[9]) +{ + s16 t = le16_to_cpup((__le16 *)rom); + + return t*1000/16; +} + +static inline int w1_DS18S20_convert_temp(u8 rom[9]) +{ + int t, h; + + if (!rom[7]) + return 0; + + if (rom[1] == 0) + t = ((s32)rom[0] >> 1)*1000; + else + t = 1000*(-1*(s32)(0x100-rom[0]) >> 1); + + t -= 250; + h = 1000*((s32)rom[7] - (s32)rom[6]); + h /= (s32)rom[7]; + t += h; + + return t; +} + +static inline int w1_convert_temp(u8 rom[9], u8 fid) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) + if (w1_therm_families[i].f->fid == fid) + return w1_therm_families[i].convert(rom); + + return 0; +} + +static ssize_t w1_slave_store(struct device *device, + struct device_attribute *attr, const char *buf, + size_t size) +{ + int val, ret; + struct w1_slave *sl = dev_to_w1_slave(device); + int i; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) { + if (w1_therm_families[i].f->fid == sl->family->fid) { + /* zero value indicates to write current configuration to eeprom */ + if (val == 0) + ret = w1_therm_families[i].eeprom(device); + else + ret = w1_therm_families[i].precision(device, val); + break; + } + } + return ret ? : size; +} + +static ssize_t read_therm(struct device *device, + struct w1_slave *sl, struct therm_info *info) +{ + struct w1_master *dev = sl->master; + u8 external_power; + int ret, max_trying = 10; + u8 *family_data = sl->family_data; + + if (!family_data) { + ret = -ENODEV; + goto error; + } + + /* prevent the slave from going away in sleep */ + atomic_inc(THERM_REFCNT(family_data)); + + ret = mutex_lock_interruptible(&dev->bus_mutex); + if (ret != 0) + goto dec_refcnt; + + memset(info->rom, 0, sizeof(info->rom)); + + while (max_trying--) { + + info->verdict = 0; + info->crc = 0; + + if (!w1_reset_select_slave(sl)) { + int count = 0; + unsigned int tm = 750; + unsigned long sleep_rem; + + w1_write_8(dev, W1_READ_PSUPPLY); + external_power = w1_read_8(dev); + + if (w1_reset_select_slave(sl)) + continue; + + /* 750ms strong pullup (or delay) after the convert */ + if (w1_strong_pullup == 2 || + (!external_power && w1_strong_pullup)) + w1_next_pullup(dev, tm); + + w1_write_8(dev, W1_CONVERT_TEMP); + + if (external_power) { + mutex_unlock(&dev->bus_mutex); + + sleep_rem = msleep_interruptible(tm); + if (sleep_rem != 0) { + ret = -EINTR; + goto dec_refcnt; + } + + ret = mutex_lock_interruptible(&dev->bus_mutex); + if (ret != 0) + goto dec_refcnt; + } else if (!w1_strong_pullup) { + sleep_rem = msleep_interruptible(tm); + if (sleep_rem != 0) { + ret = -EINTR; + goto mt_unlock; + } + } + + if (!w1_reset_select_slave(sl)) { + + w1_write_8(dev, W1_READ_SCRATCHPAD); + count = w1_read_block(dev, info->rom, 9); + if (count != 9) { + dev_warn(device, "w1_read_block() " + "returned %u instead of 9.\n", + count); + } + + info->crc = w1_calc_crc8(info->rom, 8); + + if (info->rom[8] == info->crc) + info->verdict = 1; + } + } + + if (info->verdict) + break; + } + +mt_unlock: + mutex_unlock(&dev->bus_mutex); +dec_refcnt: + atomic_dec(THERM_REFCNT(family_data)); +error: + return ret; +} + +static ssize_t w1_slave_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct therm_info info; + u8 *family_data = sl->family_data; + int ret, i; + ssize_t c = PAGE_SIZE; + u8 fid = sl->family->fid; + + ret = read_therm(device, sl, &info); + if (ret) + return ret; + + for (i = 0; i < 9; ++i) + c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", info.rom[i]); + c -= snprintf(buf + PAGE_SIZE - c, c, ": crc=%02x %s\n", + info.crc, (info.verdict) ? "YES" : "NO"); + if (info.verdict) + memcpy(family_data, info.rom, sizeof(info.rom)); + else + dev_warn(device, "Read failed CRC check\n"); + + for (i = 0; i < 9; ++i) + c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", + ((u8 *)family_data)[i]); + + c -= snprintf(buf + PAGE_SIZE - c, c, "t=%d\n", + w1_convert_temp(info.rom, fid)); + ret = PAGE_SIZE - c; + return ret; +} + +#if IS_REACHABLE(CONFIG_HWMON) +static int w1_read_temp(struct device *device, u32 attr, int channel, + long *val) +{ + struct w1_slave *sl = dev_get_drvdata(device); + struct therm_info info; + u8 fid = sl->family->fid; + int ret; + + switch (attr) { + case hwmon_temp_input: + ret = read_therm(device, sl, &info); + if (ret) + return ret; + + if (!info.verdict) { + ret = -EIO; + return ret; + } + + *val = w1_convert_temp(info.rom, fid); + ret = 0; + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} +#endif + +#define W1_42_CHAIN 0x99 +#define W1_42_CHAIN_OFF 0x3C +#define W1_42_CHAIN_OFF_INV 0xC3 +#define W1_42_CHAIN_ON 0x5A +#define W1_42_CHAIN_ON_INV 0xA5 +#define W1_42_CHAIN_DONE 0x96 +#define W1_42_CHAIN_DONE_INV 0x69 +#define W1_42_COND_READ 0x0F +#define W1_42_SUCCESS_CONFIRM_BYTE 0xAA +#define W1_42_FINISHED_BYTE 0xFF +static ssize_t w1_seq_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + ssize_t c = PAGE_SIZE; + int rv; + int i; + u8 ack; + u64 rn; + struct w1_reg_num *reg_num; + int seq = 0; + + mutex_lock(&sl->master->bus_mutex); + /* Place all devices in CHAIN state */ + if (w1_reset_bus(sl->master)) + goto error; + w1_write_8(sl->master, W1_SKIP_ROM); + w1_write_8(sl->master, W1_42_CHAIN); + w1_write_8(sl->master, W1_42_CHAIN_ON); + w1_write_8(sl->master, W1_42_CHAIN_ON_INV); + msleep(sl->master->pullup_duration); + + /* check for acknowledgment */ + ack = w1_read_8(sl->master); + if (ack != W1_42_SUCCESS_CONFIRM_BYTE) + goto error; + + /* In case the bus fails to send 0xFF, limit*/ + for (i = 0; i <= 64; i++) { + if (w1_reset_bus(sl->master)) + goto error; + + w1_write_8(sl->master, W1_42_COND_READ); + rv = w1_read_block(sl->master, (u8 *)&rn, 8); + reg_num = (struct w1_reg_num *) &rn; + if (reg_num->family == W1_42_FINISHED_BYTE) + break; + if (sl->reg_num.id == reg_num->id) + seq = i; + + if (w1_reset_bus(sl->master)) + goto error; + + /* Put the device into chain DONE state */ + w1_write_8(sl->master, W1_MATCH_ROM); + w1_write_block(sl->master, (u8 *)&rn, 8); + w1_write_8(sl->master, W1_42_CHAIN); + w1_write_8(sl->master, W1_42_CHAIN_DONE); + w1_write_8(sl->master, W1_42_CHAIN_DONE_INV); + + /* check for acknowledgment */ + ack = w1_read_8(sl->master); + if (ack != W1_42_SUCCESS_CONFIRM_BYTE) + goto error; + } + + /* Exit from CHAIN state */ + if (w1_reset_bus(sl->master)) + goto error; + w1_write_8(sl->master, W1_SKIP_ROM); + w1_write_8(sl->master, W1_42_CHAIN); + w1_write_8(sl->master, W1_42_CHAIN_OFF); + w1_write_8(sl->master, W1_42_CHAIN_OFF_INV); + + /* check for acknowledgment */ + ack = w1_read_8(sl->master); + if (ack != W1_42_SUCCESS_CONFIRM_BYTE) + goto error; + mutex_unlock(&sl->master->bus_mutex); + + c -= snprintf(buf + PAGE_SIZE - c, c, "%d\n", seq); + return PAGE_SIZE - c; +error: + mutex_unlock(&sl->master->bus_mutex); + return -EIO; +} + +static int __init w1_therm_init(void) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) { + err = w1_register_family(w1_therm_families[i].f); + if (err) + w1_therm_families[i].broken = 1; + } + + return 0; +} + +static void __exit w1_therm_fini(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) + if (!w1_therm_families[i].broken) + w1_unregister_family(w1_therm_families[i].f); +} + +module_init(w1_therm_init); +module_exit(w1_therm_fini); + +MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>"); +MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol, temperature family."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS18S20)); +MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS1822)); +MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS18B20)); +MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS1825)); +MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS28EA00)); diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c new file mode 100644 index 000000000..890c038c2 --- /dev/null +++ b/drivers/w1/w1.c @@ -0,0 +1,1254 @@ +/* + * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/timer.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <linux/hwmon.h> +#include <linux/of.h> + +#include <linux/atomic.h> + +#include "w1_internal.h" +#include "w1_netlink.h" + +#define W1_FAMILY_DEFAULT 0 + +static int w1_timeout = 10; +module_param_named(timeout, w1_timeout, int, 0); +MODULE_PARM_DESC(timeout, "time in seconds between automatic slave searches"); + +static int w1_timeout_us = 0; +module_param_named(timeout_us, w1_timeout_us, int, 0); +MODULE_PARM_DESC(timeout_us, + "time in microseconds between automatic slave searches"); + +/* A search stops when w1_max_slave_count devices have been found in that + * search. The next search will start over and detect the same set of devices + * on a static 1-wire bus. Memory is not allocated based on this number, just + * on the number of devices known to the kernel. Having a high number does not + * consume additional resources. As a special case, if there is only one + * device on the network and w1_max_slave_count is set to 1, the device id can + * be read directly skipping the normal slower search process. + */ +int w1_max_slave_count = 64; +module_param_named(max_slave_count, w1_max_slave_count, int, 0); +MODULE_PARM_DESC(max_slave_count, + "maximum number of slaves detected in a search"); + +int w1_max_slave_ttl = 10; +module_param_named(slave_ttl, w1_max_slave_ttl, int, 0); +MODULE_PARM_DESC(slave_ttl, + "Number of searches not seeing a slave before it will be removed"); + +DEFINE_MUTEX(w1_mlock); +LIST_HEAD(w1_masters); + +static int w1_master_match(struct device *dev, struct device_driver *drv) +{ + return 1; +} + +static int w1_master_probe(struct device *dev) +{ + return -ENODEV; +} + +static void w1_master_release(struct device *dev) +{ + struct w1_master *md = dev_to_w1_master(dev); + + dev_dbg(dev, "%s: Releasing %s.\n", __func__, md->name); + memset(md, 0, sizeof(struct w1_master) + sizeof(struct w1_bus_master)); + kfree(md); +} + +static void w1_slave_release(struct device *dev) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + + dev_dbg(dev, "%s: Releasing %s [%p]\n", __func__, sl->name, sl); + + w1_family_put(sl->family); + sl->master->slave_count--; +} + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + + return sprintf(buf, "%s\n", sl->name); +} +static DEVICE_ATTR_RO(name); + +static ssize_t id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + ssize_t count = sizeof(sl->reg_num); + + memcpy(buf, (u8 *)&sl->reg_num, count); + return count; +} +static DEVICE_ATTR_RO(id); + +static struct attribute *w1_slave_attrs[] = { + &dev_attr_name.attr, + &dev_attr_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(w1_slave); + +/* Default family */ + +static ssize_t rw_write(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); + + mutex_lock(&sl->master->mutex); + if (w1_reset_select_slave(sl)) { + count = 0; + goto out_up; + } + + w1_write_block(sl->master, buf, count); + +out_up: + mutex_unlock(&sl->master->mutex); + return count; +} + +static ssize_t rw_read(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); + + mutex_lock(&sl->master->mutex); + w1_read_block(sl->master, buf, count); + mutex_unlock(&sl->master->mutex); + return count; +} + +static BIN_ATTR_RW(rw, PAGE_SIZE); + +static struct bin_attribute *w1_slave_bin_attrs[] = { + &bin_attr_rw, + NULL, +}; + +static const struct attribute_group w1_slave_default_group = { + .bin_attrs = w1_slave_bin_attrs, +}; + +static const struct attribute_group *w1_slave_default_groups[] = { + &w1_slave_default_group, + NULL, +}; + +static struct w1_family_ops w1_default_fops = { + .groups = w1_slave_default_groups, +}; + +static struct w1_family w1_default_family = { + .fops = &w1_default_fops, +}; + +static int w1_uevent(struct device *dev, struct kobj_uevent_env *env); + +static struct bus_type w1_bus_type = { + .name = "w1", + .match = w1_master_match, + .uevent = w1_uevent, +}; + +struct device_driver w1_master_driver = { + .name = "w1_master_driver", + .bus = &w1_bus_type, + .probe = w1_master_probe, +}; + +struct device w1_master_device = { + .parent = NULL, + .bus = &w1_bus_type, + .init_name = "w1 bus master", + .driver = &w1_master_driver, + .release = &w1_master_release +}; + +static struct device_driver w1_slave_driver = { + .name = "w1_slave_driver", + .bus = &w1_bus_type, +}; + +#if 0 +struct device w1_slave_device = { + .parent = NULL, + .bus = &w1_bus_type, + .init_name = "w1 bus slave", + .driver = &w1_slave_driver, + .release = &w1_slave_release +}; +#endif /* 0 */ + +static ssize_t w1_master_attribute_show_name(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w1_master *md = dev_to_w1_master(dev); + ssize_t count; + + mutex_lock(&md->mutex); + count = sprintf(buf, "%s\n", md->name); + mutex_unlock(&md->mutex); + + return count; +} + +static ssize_t w1_master_attribute_store_search(struct device * dev, + struct device_attribute *attr, + const char * buf, size_t count) +{ + long tmp; + struct w1_master *md = dev_to_w1_master(dev); + int ret; + + ret = kstrtol(buf, 0, &tmp); + if (ret) + return ret; + + mutex_lock(&md->mutex); + md->search_count = tmp; + mutex_unlock(&md->mutex); + /* Only wake if it is going to be searching. */ + if (tmp) + wake_up_process(md->thread); + + return count; +} + +static ssize_t w1_master_attribute_show_search(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct w1_master *md = dev_to_w1_master(dev); + ssize_t count; + + mutex_lock(&md->mutex); + count = sprintf(buf, "%d\n", md->search_count); + mutex_unlock(&md->mutex); + + return count; +} + +static ssize_t w1_master_attribute_store_pullup(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + long tmp; + struct w1_master *md = dev_to_w1_master(dev); + int ret; + + ret = kstrtol(buf, 0, &tmp); + if (ret) + return ret; + + mutex_lock(&md->mutex); + md->enable_pullup = tmp; + mutex_unlock(&md->mutex); + + return count; +} + +static ssize_t w1_master_attribute_show_pullup(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct w1_master *md = dev_to_w1_master(dev); + ssize_t count; + + mutex_lock(&md->mutex); + count = sprintf(buf, "%d\n", md->enable_pullup); + mutex_unlock(&md->mutex); + + return count; +} + +static ssize_t w1_master_attribute_show_pointer(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w1_master *md = dev_to_w1_master(dev); + ssize_t count; + + mutex_lock(&md->mutex); + count = sprintf(buf, "0x%p\n", md->bus_master); + mutex_unlock(&md->mutex); + return count; +} + +static ssize_t w1_master_attribute_show_timeout(struct device *dev, struct device_attribute *attr, char *buf) +{ + ssize_t count; + count = sprintf(buf, "%d\n", w1_timeout); + return count; +} + +static ssize_t w1_master_attribute_show_timeout_us(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count; + count = sprintf(buf, "%d\n", w1_timeout_us); + return count; +} + +static ssize_t w1_master_attribute_store_max_slave_count(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int tmp; + struct w1_master *md = dev_to_w1_master(dev); + + if (kstrtoint(buf, 0, &tmp) || tmp < 1) + return -EINVAL; + + mutex_lock(&md->mutex); + md->max_slave_count = tmp; + /* allow each time the max_slave_count is updated */ + clear_bit(W1_WARN_MAX_COUNT, &md->flags); + mutex_unlock(&md->mutex); + + return count; +} + +static ssize_t w1_master_attribute_show_max_slave_count(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w1_master *md = dev_to_w1_master(dev); + ssize_t count; + + mutex_lock(&md->mutex); + count = sprintf(buf, "%d\n", md->max_slave_count); + mutex_unlock(&md->mutex); + return count; +} + +static ssize_t w1_master_attribute_show_attempts(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w1_master *md = dev_to_w1_master(dev); + ssize_t count; + + mutex_lock(&md->mutex); + count = sprintf(buf, "%lu\n", md->attempts); + mutex_unlock(&md->mutex); + return count; +} + +static ssize_t w1_master_attribute_show_slave_count(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w1_master *md = dev_to_w1_master(dev); + ssize_t count; + + mutex_lock(&md->mutex); + count = sprintf(buf, "%d\n", md->slave_count); + mutex_unlock(&md->mutex); + return count; +} + +static ssize_t w1_master_attribute_show_slaves(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct w1_master *md = dev_to_w1_master(dev); + int c = PAGE_SIZE; + struct list_head *ent, *n; + struct w1_slave *sl = NULL; + + mutex_lock(&md->list_mutex); + + list_for_each_safe(ent, n, &md->slist) { + sl = list_entry(ent, struct w1_slave, w1_slave_entry); + + c -= snprintf(buf + PAGE_SIZE - c, c, "%s\n", sl->name); + } + if (!sl) + c -= snprintf(buf + PAGE_SIZE - c, c, "not found.\n"); + + mutex_unlock(&md->list_mutex); + + return PAGE_SIZE - c; +} + +static ssize_t w1_master_attribute_show_add(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int c = PAGE_SIZE; + c -= snprintf(buf+PAGE_SIZE - c, c, + "write device id xx-xxxxxxxxxxxx to add slave\n"); + return PAGE_SIZE - c; +} + +static int w1_atoreg_num(struct device *dev, const char *buf, size_t count, + struct w1_reg_num *rn) +{ + unsigned int family; + unsigned long long id; + int i; + u64 rn64_le; + + /* The CRC value isn't read from the user because the sysfs directory + * doesn't include it and most messages from the bus search don't + * print it either. It would be unreasonable for the user to then + * provide it. + */ + const char *error_msg = "bad slave string format, expecting " + "ff-dddddddddddd\n"; + + if (buf[2] != '-') { + dev_err(dev, "%s", error_msg); + return -EINVAL; + } + i = sscanf(buf, "%02x-%012llx", &family, &id); + if (i != 2) { + dev_err(dev, "%s", error_msg); + return -EINVAL; + } + rn->family = family; + rn->id = id; + + rn64_le = cpu_to_le64(*(u64 *)rn); + rn->crc = w1_calc_crc8((u8 *)&rn64_le, 7); + +#if 0 + dev_info(dev, "With CRC device is %02x.%012llx.%02x.\n", + rn->family, (unsigned long long)rn->id, rn->crc); +#endif + + return 0; +} + +/* Searches the slaves in the w1_master and returns a pointer or NULL. + * Note: must not hold list_mutex + */ +struct w1_slave *w1_slave_search_device(struct w1_master *dev, + struct w1_reg_num *rn) +{ + struct w1_slave *sl; + mutex_lock(&dev->list_mutex); + list_for_each_entry(sl, &dev->slist, w1_slave_entry) { + if (sl->reg_num.family == rn->family && + sl->reg_num.id == rn->id && + sl->reg_num.crc == rn->crc) { + mutex_unlock(&dev->list_mutex); + return sl; + } + } + mutex_unlock(&dev->list_mutex); + return NULL; +} + +static ssize_t w1_master_attribute_store_add(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w1_master *md = dev_to_w1_master(dev); + struct w1_reg_num rn; + struct w1_slave *sl; + ssize_t result = count; + + if (w1_atoreg_num(dev, buf, count, &rn)) + return -EINVAL; + + mutex_lock(&md->mutex); + sl = w1_slave_search_device(md, &rn); + /* It would be nice to do a targeted search one the one-wire bus + * for the new device to see if it is out there or not. But the + * current search doesn't support that. + */ + if (sl) { + dev_info(dev, "Device %s already exists\n", sl->name); + result = -EINVAL; + } else { + w1_attach_slave_device(md, &rn); + } + mutex_unlock(&md->mutex); + + return result; +} + +static ssize_t w1_master_attribute_show_remove(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int c = PAGE_SIZE; + c -= snprintf(buf+PAGE_SIZE - c, c, + "write device id xx-xxxxxxxxxxxx to remove slave\n"); + return PAGE_SIZE - c; +} + +static ssize_t w1_master_attribute_store_remove(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w1_master *md = dev_to_w1_master(dev); + struct w1_reg_num rn; + struct w1_slave *sl; + ssize_t result = count; + + if (w1_atoreg_num(dev, buf, count, &rn)) + return -EINVAL; + + mutex_lock(&md->mutex); + sl = w1_slave_search_device(md, &rn); + if (sl) { + result = w1_slave_detach(sl); + /* refcnt 0 means it was detached in the call */ + if (result == 0) + result = count; + } else { + dev_info(dev, "Device %02x-%012llx doesn't exists\n", rn.family, + (unsigned long long)rn.id); + result = -EINVAL; + } + mutex_unlock(&md->mutex); + + return result; +} + +#define W1_MASTER_ATTR_RO(_name, _mode) \ + struct device_attribute w1_master_attribute_##_name = \ + __ATTR(w1_master_##_name, _mode, \ + w1_master_attribute_show_##_name, NULL) + +#define W1_MASTER_ATTR_RW(_name, _mode) \ + struct device_attribute w1_master_attribute_##_name = \ + __ATTR(w1_master_##_name, _mode, \ + w1_master_attribute_show_##_name, \ + w1_master_attribute_store_##_name) + +static W1_MASTER_ATTR_RO(name, S_IRUGO); +static W1_MASTER_ATTR_RO(slaves, S_IRUGO); +static W1_MASTER_ATTR_RO(slave_count, S_IRUGO); +static W1_MASTER_ATTR_RW(max_slave_count, S_IRUGO | S_IWUSR | S_IWGRP); +static W1_MASTER_ATTR_RO(attempts, S_IRUGO); +static W1_MASTER_ATTR_RO(timeout, S_IRUGO); +static W1_MASTER_ATTR_RO(timeout_us, S_IRUGO); +static W1_MASTER_ATTR_RO(pointer, S_IRUGO); +static W1_MASTER_ATTR_RW(search, S_IRUGO | S_IWUSR | S_IWGRP); +static W1_MASTER_ATTR_RW(pullup, S_IRUGO | S_IWUSR | S_IWGRP); +static W1_MASTER_ATTR_RW(add, S_IRUGO | S_IWUSR | S_IWGRP); +static W1_MASTER_ATTR_RW(remove, S_IRUGO | S_IWUSR | S_IWGRP); + +static struct attribute *w1_master_default_attrs[] = { + &w1_master_attribute_name.attr, + &w1_master_attribute_slaves.attr, + &w1_master_attribute_slave_count.attr, + &w1_master_attribute_max_slave_count.attr, + &w1_master_attribute_attempts.attr, + &w1_master_attribute_timeout.attr, + &w1_master_attribute_timeout_us.attr, + &w1_master_attribute_pointer.attr, + &w1_master_attribute_search.attr, + &w1_master_attribute_pullup.attr, + &w1_master_attribute_add.attr, + &w1_master_attribute_remove.attr, + NULL +}; + +static const struct attribute_group w1_master_defattr_group = { + .attrs = w1_master_default_attrs, +}; + +int w1_create_master_attributes(struct w1_master *master) +{ + return sysfs_create_group(&master->dev.kobj, &w1_master_defattr_group); +} + +void w1_destroy_master_attributes(struct w1_master *master) +{ + sysfs_remove_group(&master->dev.kobj, &w1_master_defattr_group); +} + +static int w1_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct w1_master *md = NULL; + struct w1_slave *sl = NULL; + char *event_owner, *name; + int err = 0; + + if (dev->driver == &w1_master_driver) { + md = container_of(dev, struct w1_master, dev); + event_owner = "master"; + name = md->name; + } else if (dev->driver == &w1_slave_driver) { + sl = container_of(dev, struct w1_slave, dev); + event_owner = "slave"; + name = sl->name; + } else { + dev_dbg(dev, "Unknown event.\n"); + return -EINVAL; + } + + dev_dbg(dev, "Hotplug event for %s %s, bus_id=%s.\n", + event_owner, name, dev_name(dev)); + + if (dev->driver != &w1_slave_driver || !sl) + goto end; + + err = add_uevent_var(env, "W1_FID=%02X", sl->reg_num.family); + if (err) + goto end; + + err = add_uevent_var(env, "W1_SLAVE_ID=%024LX", + (unsigned long long)sl->reg_num.id); +end: + return err; +} + +static int w1_family_notify(unsigned long action, struct w1_slave *sl) +{ + struct w1_family_ops *fops; + int err; + + fops = sl->family->fops; + + if (!fops) + return 0; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + /* if the family driver needs to initialize something... */ + if (fops->add_slave) { + err = fops->add_slave(sl); + if (err < 0) { + dev_err(&sl->dev, + "add_slave() call failed. err=%d\n", + err); + return err; + } + } + if (fops->groups) { + err = sysfs_create_groups(&sl->dev.kobj, fops->groups); + if (err) { + dev_err(&sl->dev, + "sysfs group creation failed. err=%d\n", + err); + return err; + } + } + if (IS_REACHABLE(CONFIG_HWMON) && fops->chip_info) { + struct device *hwmon + = hwmon_device_register_with_info(&sl->dev, + "w1_slave_temp", sl, + fops->chip_info, + NULL); + if (IS_ERR(hwmon)) { + dev_warn(&sl->dev, + "could not create hwmon device\n"); + } else { + sl->hwmon = hwmon; + } + } + break; + case BUS_NOTIFY_DEL_DEVICE: + if (IS_REACHABLE(CONFIG_HWMON) && fops->chip_info && + sl->hwmon) + hwmon_device_unregister(sl->hwmon); + if (fops->remove_slave) + sl->family->fops->remove_slave(sl); + if (fops->groups) + sysfs_remove_groups(&sl->dev.kobj, fops->groups); + break; + } + return 0; +} + +static int __w1_attach_slave_device(struct w1_slave *sl) +{ + int err; + + sl->dev.parent = &sl->master->dev; + sl->dev.driver = &w1_slave_driver; + sl->dev.bus = &w1_bus_type; + sl->dev.release = &w1_slave_release; + sl->dev.groups = w1_slave_groups; + sl->dev.of_node = of_find_matching_node(sl->master->dev.of_node, + sl->family->of_match_table); + + dev_set_name(&sl->dev, "%02x-%012llx", + (unsigned int) sl->reg_num.family, + (unsigned long long) sl->reg_num.id); + snprintf(&sl->name[0], sizeof(sl->name), + "%02x-%012llx", + (unsigned int) sl->reg_num.family, + (unsigned long long) sl->reg_num.id); + + dev_dbg(&sl->dev, "%s: registering %s as %p.\n", __func__, + dev_name(&sl->dev), sl); + + /* suppress for w1_family_notify before sending KOBJ_ADD */ + dev_set_uevent_suppress(&sl->dev, true); + + err = device_register(&sl->dev); + if (err < 0) { + dev_err(&sl->dev, + "Device registration [%s] failed. err=%d\n", + dev_name(&sl->dev), err); + put_device(&sl->dev); + return err; + } + w1_family_notify(BUS_NOTIFY_ADD_DEVICE, sl); + + dev_set_uevent_suppress(&sl->dev, false); + kobject_uevent(&sl->dev.kobj, KOBJ_ADD); + + mutex_lock(&sl->master->list_mutex); + list_add_tail(&sl->w1_slave_entry, &sl->master->slist); + mutex_unlock(&sl->master->list_mutex); + + return 0; +} + +int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn) +{ + struct w1_slave *sl; + struct w1_family *f; + int err; + struct w1_netlink_msg msg; + + sl = kzalloc(sizeof(struct w1_slave), GFP_KERNEL); + if (!sl) { + dev_err(&dev->dev, + "%s: failed to allocate new slave device.\n", + __func__); + return -ENOMEM; + } + + + sl->owner = THIS_MODULE; + sl->master = dev; + set_bit(W1_SLAVE_ACTIVE, &sl->flags); + + memset(&msg, 0, sizeof(msg)); + memcpy(&sl->reg_num, rn, sizeof(sl->reg_num)); + atomic_set(&sl->refcnt, 1); + atomic_inc(&sl->master->refcnt); + dev->slave_count++; + dev_info(&dev->dev, "Attaching one wire slave %02x.%012llx crc %02x\n", + rn->family, (unsigned long long)rn->id, rn->crc); + + /* slave modules need to be loaded in a context with unlocked mutex */ + mutex_unlock(&dev->mutex); + request_module("w1-family-0x%02X", rn->family); + mutex_lock(&dev->mutex); + + spin_lock(&w1_flock); + f = w1_family_registered(rn->family); + if (!f) { + f= &w1_default_family; + dev_info(&dev->dev, "Family %x for %02x.%012llx.%02x is not registered.\n", + rn->family, rn->family, + (unsigned long long)rn->id, rn->crc); + } + __w1_family_get(f); + spin_unlock(&w1_flock); + + sl->family = f; + + err = __w1_attach_slave_device(sl); + if (err < 0) { + dev_err(&dev->dev, "%s: Attaching %s failed.\n", __func__, + sl->name); + dev->slave_count--; + w1_family_put(sl->family); + atomic_dec(&sl->master->refcnt); + kfree(sl); + return err; + } + + sl->ttl = dev->slave_ttl; + + memcpy(msg.id.id, rn, sizeof(msg.id)); + msg.type = W1_SLAVE_ADD; + w1_netlink_send(dev, &msg); + + return 0; +} + +int w1_unref_slave(struct w1_slave *sl) +{ + struct w1_master *dev = sl->master; + int refcnt; + mutex_lock(&dev->list_mutex); + refcnt = atomic_sub_return(1, &sl->refcnt); + if (refcnt == 0) { + struct w1_netlink_msg msg; + + dev_dbg(&sl->dev, "%s: detaching %s [%p].\n", __func__, + sl->name, sl); + + list_del(&sl->w1_slave_entry); + + memset(&msg, 0, sizeof(msg)); + memcpy(msg.id.id, &sl->reg_num, sizeof(msg.id)); + msg.type = W1_SLAVE_REMOVE; + w1_netlink_send(sl->master, &msg); + + w1_family_notify(BUS_NOTIFY_DEL_DEVICE, sl); + device_unregister(&sl->dev); + #ifdef DEBUG + memset(sl, 0, sizeof(*sl)); + #endif + kfree(sl); + } + atomic_dec(&dev->refcnt); + mutex_unlock(&dev->list_mutex); + return refcnt; +} + +int w1_slave_detach(struct w1_slave *sl) +{ + /* Only detach a slave once as it decreases the refcnt each time. */ + int destroy_now; + mutex_lock(&sl->master->list_mutex); + destroy_now = !test_bit(W1_SLAVE_DETACH, &sl->flags); + set_bit(W1_SLAVE_DETACH, &sl->flags); + mutex_unlock(&sl->master->list_mutex); + + if (destroy_now) + destroy_now = !w1_unref_slave(sl); + return destroy_now ? 0 : -EBUSY; +} + +struct w1_master *w1_search_master_id(u32 id) +{ + struct w1_master *dev; + int found = 0; + + mutex_lock(&w1_mlock); + list_for_each_entry(dev, &w1_masters, w1_master_entry) { + if (dev->id == id) { + found = 1; + atomic_inc(&dev->refcnt); + break; + } + } + mutex_unlock(&w1_mlock); + + return (found)?dev:NULL; +} + +struct w1_slave *w1_search_slave(struct w1_reg_num *id) +{ + struct w1_master *dev; + struct w1_slave *sl = NULL; + int found = 0; + + mutex_lock(&w1_mlock); + list_for_each_entry(dev, &w1_masters, w1_master_entry) { + mutex_lock(&dev->list_mutex); + list_for_each_entry(sl, &dev->slist, w1_slave_entry) { + if (sl->reg_num.family == id->family && + sl->reg_num.id == id->id && + sl->reg_num.crc == id->crc) { + found = 1; + atomic_inc(&dev->refcnt); + atomic_inc(&sl->refcnt); + break; + } + } + mutex_unlock(&dev->list_mutex); + + if (found) + break; + } + mutex_unlock(&w1_mlock); + + return (found)?sl:NULL; +} + +void w1_reconnect_slaves(struct w1_family *f, int attach) +{ + struct w1_slave *sl, *sln; + struct w1_master *dev; + + mutex_lock(&w1_mlock); + list_for_each_entry(dev, &w1_masters, w1_master_entry) { + dev_dbg(&dev->dev, "Reconnecting slaves in device %s " + "for family %02x.\n", dev->name, f->fid); + mutex_lock(&dev->mutex); + mutex_lock(&dev->list_mutex); + list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) { + /* If it is a new family, slaves with the default + * family driver and are that family will be + * connected. If the family is going away, devices + * matching that family are reconneced. + */ + if ((attach && sl->family->fid == W1_FAMILY_DEFAULT + && sl->reg_num.family == f->fid) || + (!attach && sl->family->fid == f->fid)) { + struct w1_reg_num rn; + + mutex_unlock(&dev->list_mutex); + memcpy(&rn, &sl->reg_num, sizeof(rn)); + /* If it was already in use let the automatic + * scan pick it up again later. + */ + if (!w1_slave_detach(sl)) + w1_attach_slave_device(dev, &rn); + mutex_lock(&dev->list_mutex); + } + } + dev_dbg(&dev->dev, "Reconnecting slaves in device %s " + "has been finished.\n", dev->name); + mutex_unlock(&dev->list_mutex); + mutex_unlock(&dev->mutex); + } + mutex_unlock(&w1_mlock); +} + +void w1_slave_found(struct w1_master *dev, u64 rn) +{ + struct w1_slave *sl; + struct w1_reg_num *tmp; + u64 rn_le = cpu_to_le64(rn); + + atomic_inc(&dev->refcnt); + + tmp = (struct w1_reg_num *) &rn; + + sl = w1_slave_search_device(dev, tmp); + if (sl) { + set_bit(W1_SLAVE_ACTIVE, &sl->flags); + } else { + if (rn && tmp->crc == w1_calc_crc8((u8 *)&rn_le, 7)) + w1_attach_slave_device(dev, tmp); + } + + atomic_dec(&dev->refcnt); +} + +/** + * w1_search() - Performs a ROM Search & registers any devices found. + * @dev: The master device to search + * @search_type: W1_SEARCH to search all devices, or W1_ALARM_SEARCH + * to return only devices in the alarmed state + * @cb: Function to call when a device is found + * + * The 1-wire search is a simple binary tree search. + * For each bit of the address, we read two bits and write one bit. + * The bit written will put to sleep all devies that don't match that bit. + * When the two reads differ, the direction choice is obvious. + * When both bits are 0, we must choose a path to take. + * When we can scan all 64 bits without having to choose a path, we are done. + * + * See "Application note 187 1-wire search algorithm" at www.maxim-ic.com + * + */ +void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb) +{ + u64 last_rn, rn, tmp64; + int i, slave_count = 0; + int last_zero, last_device; + int search_bit, desc_bit; + u8 triplet_ret = 0; + + search_bit = 0; + rn = dev->search_id; + last_rn = 0; + last_device = 0; + last_zero = -1; + + desc_bit = 64; + + while ( !last_device && (slave_count++ < dev->max_slave_count) ) { + last_rn = rn; + rn = 0; + + /* + * Reset bus and all 1-wire device state machines + * so they can respond to our requests. + * + * Return 0 - device(s) present, 1 - no devices present. + */ + mutex_lock(&dev->bus_mutex); + if (w1_reset_bus(dev)) { + mutex_unlock(&dev->bus_mutex); + dev_dbg(&dev->dev, "No devices present on the wire.\n"); + break; + } + + /* Do fast search on single slave bus */ + if (dev->max_slave_count == 1) { + int rv; + w1_write_8(dev, W1_READ_ROM); + rv = w1_read_block(dev, (u8 *)&rn, 8); + mutex_unlock(&dev->bus_mutex); + + if (rv == 8 && rn) + cb(dev, rn); + + break; + } + + /* Start the search */ + w1_write_8(dev, search_type); + for (i = 0; i < 64; ++i) { + /* Determine the direction/search bit */ + if (i == desc_bit) + search_bit = 1; /* took the 0 path last time, so take the 1 path */ + else if (i > desc_bit) + search_bit = 0; /* take the 0 path on the next branch */ + else + search_bit = ((last_rn >> i) & 0x1); + + /* Read two bits and write one bit */ + triplet_ret = w1_triplet(dev, search_bit); + + /* quit if no device responded */ + if ( (triplet_ret & 0x03) == 0x03 ) + break; + + /* If both directions were valid, and we took the 0 path... */ + if (triplet_ret == 0) + last_zero = i; + + /* extract the direction taken & update the device number */ + tmp64 = (triplet_ret >> 2); + rn |= (tmp64 << i); + + if (test_bit(W1_ABORT_SEARCH, &dev->flags)) { + mutex_unlock(&dev->bus_mutex); + dev_dbg(&dev->dev, "Abort w1_search\n"); + return; + } + } + mutex_unlock(&dev->bus_mutex); + + if ( (triplet_ret & 0x03) != 0x03 ) { + if ((desc_bit == last_zero) || (last_zero < 0)) { + last_device = 1; + dev->search_id = 0; + } else { + dev->search_id = rn; + } + desc_bit = last_zero; + cb(dev, rn); + } + + if (!last_device && slave_count == dev->max_slave_count && + !test_bit(W1_WARN_MAX_COUNT, &dev->flags)) { + /* Only max_slave_count will be scanned in a search, + * but it will start where it left off next search + * until all ids are identified and then it will start + * over. A continued search will report the previous + * last id as the first id (provided it is still on the + * bus). + */ + dev_info(&dev->dev, "%s: max_slave_count %d reached, " + "will continue next search.\n", __func__, + dev->max_slave_count); + set_bit(W1_WARN_MAX_COUNT, &dev->flags); + } + } +} + +void w1_search_process_cb(struct w1_master *dev, u8 search_type, + w1_slave_found_callback cb) +{ + struct w1_slave *sl, *sln; + + mutex_lock(&dev->list_mutex); + list_for_each_entry(sl, &dev->slist, w1_slave_entry) + clear_bit(W1_SLAVE_ACTIVE, &sl->flags); + mutex_unlock(&dev->list_mutex); + + w1_search_devices(dev, search_type, cb); + + mutex_lock(&dev->list_mutex); + list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) { + if (!test_bit(W1_SLAVE_ACTIVE, &sl->flags) && !--sl->ttl) { + mutex_unlock(&dev->list_mutex); + w1_slave_detach(sl); + mutex_lock(&dev->list_mutex); + } + else if (test_bit(W1_SLAVE_ACTIVE, &sl->flags)) + sl->ttl = dev->slave_ttl; + } + mutex_unlock(&dev->list_mutex); + + if (dev->search_count > 0) + dev->search_count--; +} + +static void w1_search_process(struct w1_master *dev, u8 search_type) +{ + w1_search_process_cb(dev, search_type, w1_slave_found); +} + +/** + * w1_process_callbacks() - execute each dev->async_list callback entry + * @dev: w1_master device + * + * The w1 master list_mutex must be held. + * + * Return: 1 if there were commands to executed 0 otherwise + */ +int w1_process_callbacks(struct w1_master *dev) +{ + int ret = 0; + struct w1_async_cmd *async_cmd, *async_n; + + /* The list can be added to in another thread, loop until it is empty */ + while (!list_empty(&dev->async_list)) { + list_for_each_entry_safe(async_cmd, async_n, &dev->async_list, + async_entry) { + /* drop the lock, if it is a search it can take a long + * time */ + mutex_unlock(&dev->list_mutex); + async_cmd->cb(dev, async_cmd); + ret = 1; + mutex_lock(&dev->list_mutex); + } + } + return ret; +} + +int w1_process(void *data) +{ + struct w1_master *dev = (struct w1_master *) data; + /* As long as w1_timeout is only set by a module parameter the sleep + * time can be calculated in jiffies once. + */ + const unsigned long jtime = + usecs_to_jiffies(w1_timeout * 1000000 + w1_timeout_us); + /* remainder if it woke up early */ + unsigned long jremain = 0; + + for (;;) { + + if (!jremain && dev->search_count) { + mutex_lock(&dev->mutex); + w1_search_process(dev, W1_SEARCH); + mutex_unlock(&dev->mutex); + } + + mutex_lock(&dev->list_mutex); + /* Note, w1_process_callback drops the lock while processing, + * but locks it again before returning. + */ + if (!w1_process_callbacks(dev) && jremain) { + /* a wake up is either to stop the thread, process + * callbacks, or search, it isn't process callbacks, so + * schedule a search. + */ + jremain = 1; + } + + __set_current_state(TASK_INTERRUPTIBLE); + + /* hold list_mutex until after interruptible to prevent loosing + * the wakeup signal when async_cmd is added. + */ + mutex_unlock(&dev->list_mutex); + + if (kthread_should_stop()) + break; + + /* Only sleep when the search is active. */ + if (dev->search_count) { + if (!jremain) + jremain = jtime; + jremain = schedule_timeout(jremain); + } + else + schedule(); + } + + atomic_dec(&dev->refcnt); + + return 0; +} + +static int __init w1_init(void) +{ + int retval; + + pr_info("Driver for 1-wire Dallas network protocol.\n"); + + w1_init_netlink(); + + retval = bus_register(&w1_bus_type); + if (retval) { + pr_err("Failed to register bus. err=%d.\n", retval); + goto err_out_exit_init; + } + + retval = driver_register(&w1_master_driver); + if (retval) { + pr_err("Failed to register master driver. err=%d.\n", + retval); + goto err_out_bus_unregister; + } + + retval = driver_register(&w1_slave_driver); + if (retval) { + pr_err("Failed to register slave driver. err=%d.\n", + retval); + goto err_out_master_unregister; + } + + return 0; + +#if 0 +/* For undoing the slave register if there was a step after it. */ +err_out_slave_unregister: + driver_unregister(&w1_slave_driver); +#endif + +err_out_master_unregister: + driver_unregister(&w1_master_driver); + +err_out_bus_unregister: + bus_unregister(&w1_bus_type); + +err_out_exit_init: + return retval; +} + +static void __exit w1_fini(void) +{ + struct w1_master *dev; + + /* Set netlink removal messages and some cleanup */ + list_for_each_entry(dev, &w1_masters, w1_master_entry) + __w1_remove_master_device(dev); + + w1_fini_netlink(); + + driver_unregister(&w1_slave_driver); + driver_unregister(&w1_master_driver); + bus_unregister(&w1_bus_type); +} + +module_init(w1_init); +module_exit(w1_fini); + +MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>"); +MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol."); +MODULE_LICENSE("GPL"); diff --git a/drivers/w1/w1_family.c b/drivers/w1/w1_family.c new file mode 100644 index 000000000..f14ab0b34 --- /dev/null +++ b/drivers/w1/w1_family.c @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/sched/signal.h> +#include <linux/delay.h> +#include <linux/export.h> + +#include "w1_internal.h" + +DEFINE_SPINLOCK(w1_flock); +static LIST_HEAD(w1_families); + +/** + * w1_register_family() - register a device family driver + * @newf: family to register + */ +int w1_register_family(struct w1_family *newf) +{ + struct list_head *ent, *n; + struct w1_family *f; + int ret = 0; + + spin_lock(&w1_flock); + list_for_each_safe(ent, n, &w1_families) { + f = list_entry(ent, struct w1_family, family_entry); + + if (f->fid == newf->fid) { + ret = -EEXIST; + break; + } + } + + if (!ret) { + atomic_set(&newf->refcnt, 0); + list_add_tail(&newf->family_entry, &w1_families); + } + spin_unlock(&w1_flock); + + /* check default devices against the new set of drivers */ + w1_reconnect_slaves(newf, 1); + + return ret; +} +EXPORT_SYMBOL(w1_register_family); + +/** + * w1_unregister_family() - unregister a device family driver + * @fent: family to unregister + */ +void w1_unregister_family(struct w1_family *fent) +{ + struct list_head *ent, *n; + struct w1_family *f; + + spin_lock(&w1_flock); + list_for_each_safe(ent, n, &w1_families) { + f = list_entry(ent, struct w1_family, family_entry); + + if (f->fid == fent->fid) { + list_del(&fent->family_entry); + break; + } + } + spin_unlock(&w1_flock); + + /* deatch devices using this family code */ + w1_reconnect_slaves(fent, 0); + + while (atomic_read(&fent->refcnt)) { + pr_info("Waiting for family %u to become free: refcnt=%d.\n", + fent->fid, atomic_read(&fent->refcnt)); + + if (msleep_interruptible(1000)) + flush_signals(current); + } +} +EXPORT_SYMBOL(w1_unregister_family); + +/* + * Should be called under w1_flock held. + */ +struct w1_family * w1_family_registered(u8 fid) +{ + struct list_head *ent, *n; + struct w1_family *f = NULL; + int ret = 0; + + list_for_each_safe(ent, n, &w1_families) { + f = list_entry(ent, struct w1_family, family_entry); + + if (f->fid == fid) { + ret = 1; + break; + } + } + + return (ret) ? f : NULL; +} + +static void __w1_family_put(struct w1_family *f) +{ + atomic_dec(&f->refcnt); +} + +void w1_family_put(struct w1_family *f) +{ + spin_lock(&w1_flock); + __w1_family_put(f); + spin_unlock(&w1_flock); +} + +#if 0 +void w1_family_get(struct w1_family *f) +{ + spin_lock(&w1_flock); + __w1_family_get(f); + spin_unlock(&w1_flock); +} +#endif /* 0 */ + +void __w1_family_get(struct w1_family *f) +{ + smp_mb__before_atomic(); + atomic_inc(&f->refcnt); + smp_mb__after_atomic(); +} diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c new file mode 100644 index 000000000..1c776178f --- /dev/null +++ b/drivers/w1/w1_int.c @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> +#include <linux/export.h> +#include <linux/moduleparam.h> + +#include "w1_internal.h" +#include "w1_netlink.h" + +static int w1_search_count = -1; /* Default is continual scan */ +module_param_named(search_count, w1_search_count, int, 0); + +static int w1_enable_pullup = 1; +module_param_named(enable_pullup, w1_enable_pullup, int, 0); + +static struct w1_master *w1_alloc_dev(u32 id, int slave_count, int slave_ttl, + struct device_driver *driver, + struct device *device) +{ + struct w1_master *dev; + int err; + + /* + * We are in process context(kernel thread), so can sleep. + */ + dev = kzalloc(sizeof(struct w1_master) + sizeof(struct w1_bus_master), GFP_KERNEL); + if (!dev) { + pr_err("Failed to allocate %zd bytes for new w1 device.\n", + sizeof(struct w1_master)); + return NULL; + } + + + dev->bus_master = (struct w1_bus_master *)(dev + 1); + + dev->owner = THIS_MODULE; + dev->max_slave_count = slave_count; + dev->slave_count = 0; + dev->attempts = 0; + dev->initialized = 0; + dev->id = id; + dev->slave_ttl = slave_ttl; + dev->search_count = w1_search_count; + dev->enable_pullup = w1_enable_pullup; + + /* 1 for w1_process to decrement + * 1 for __w1_remove_master_device to decrement + */ + atomic_set(&dev->refcnt, 2); + + INIT_LIST_HEAD(&dev->slist); + INIT_LIST_HEAD(&dev->async_list); + mutex_init(&dev->mutex); + mutex_init(&dev->bus_mutex); + mutex_init(&dev->list_mutex); + + memcpy(&dev->dev, device, sizeof(struct device)); + dev_set_name(&dev->dev, "w1_bus_master%u", dev->id); + snprintf(dev->name, sizeof(dev->name), "w1_bus_master%u", dev->id); + dev->dev.init_name = dev->name; + + dev->driver = driver; + + dev->seq = 1; + + err = device_register(&dev->dev); + if (err) { + pr_err("Failed to register master device. err=%d\n", err); + put_device(&dev->dev); + dev = NULL; + } + + return dev; +} + +static void w1_free_dev(struct w1_master *dev) +{ + device_unregister(&dev->dev); +} + +/** + * w1_add_master_device() - registers a new master device + * @master: master bus device to register + */ +int w1_add_master_device(struct w1_bus_master *master) +{ + struct w1_master *dev, *entry; + int retval = 0; + struct w1_netlink_msg msg; + int id, found; + + /* validate minimum functionality */ + if (!(master->touch_bit && master->reset_bus) && + !(master->write_bit && master->read_bit) && + !(master->write_byte && master->read_byte && master->reset_bus)) { + pr_err("w1_add_master_device: invalid function set\n"); + return(-EINVAL); + } + + /* Lock until the device is added (or not) to w1_masters. */ + mutex_lock(&w1_mlock); + /* Search for the first available id (starting at 1). */ + id = 0; + do { + ++id; + found = 0; + list_for_each_entry(entry, &w1_masters, w1_master_entry) { + if (entry->id == id) { + found = 1; + break; + } + } + } while (found); + + dev = w1_alloc_dev(id, w1_max_slave_count, w1_max_slave_ttl, + &w1_master_driver, &w1_master_device); + if (!dev) { + mutex_unlock(&w1_mlock); + return -ENOMEM; + } + + retval = w1_create_master_attributes(dev); + if (retval) { + mutex_unlock(&w1_mlock); + goto err_out_free_dev; + } + + memcpy(dev->bus_master, master, sizeof(struct w1_bus_master)); + + dev->initialized = 1; + + dev->thread = kthread_run(&w1_process, dev, "%s", dev->name); + if (IS_ERR(dev->thread)) { + retval = PTR_ERR(dev->thread); + dev_err(&dev->dev, + "Failed to create new kernel thread. err=%d\n", + retval); + mutex_unlock(&w1_mlock); + goto err_out_rm_attr; + } + + list_add(&dev->w1_master_entry, &w1_masters); + mutex_unlock(&w1_mlock); + + memset(&msg, 0, sizeof(msg)); + msg.id.mst.id = dev->id; + msg.type = W1_MASTER_ADD; + w1_netlink_send(dev, &msg); + + return 0; + +#if 0 /* Thread cleanup code, not required currently. */ +err_out_kill_thread: + set_bit(W1_ABORT_SEARCH, &dev->flags); + kthread_stop(dev->thread); +#endif +err_out_rm_attr: + w1_destroy_master_attributes(dev); +err_out_free_dev: + w1_free_dev(dev); + + return retval; +} +EXPORT_SYMBOL(w1_add_master_device); + +void __w1_remove_master_device(struct w1_master *dev) +{ + struct w1_netlink_msg msg; + struct w1_slave *sl, *sln; + + mutex_lock(&w1_mlock); + list_del(&dev->w1_master_entry); + mutex_unlock(&w1_mlock); + + set_bit(W1_ABORT_SEARCH, &dev->flags); + kthread_stop(dev->thread); + + mutex_lock(&dev->mutex); + mutex_lock(&dev->list_mutex); + list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) { + mutex_unlock(&dev->list_mutex); + w1_slave_detach(sl); + mutex_lock(&dev->list_mutex); + } + w1_destroy_master_attributes(dev); + mutex_unlock(&dev->list_mutex); + mutex_unlock(&dev->mutex); + atomic_dec(&dev->refcnt); + + while (atomic_read(&dev->refcnt)) { + dev_info(&dev->dev, "Waiting for %s to become free: refcnt=%d.\n", + dev->name, atomic_read(&dev->refcnt)); + + if (msleep_interruptible(1000)) + flush_signals(current); + mutex_lock(&dev->list_mutex); + w1_process_callbacks(dev); + mutex_unlock(&dev->list_mutex); + } + mutex_lock(&dev->list_mutex); + w1_process_callbacks(dev); + mutex_unlock(&dev->list_mutex); + + memset(&msg, 0, sizeof(msg)); + msg.id.mst.id = dev->id; + msg.type = W1_MASTER_REMOVE; + w1_netlink_send(dev, &msg); + + w1_free_dev(dev); +} + +/** + * w1_remove_master_device() - unregister a master device + * @bm: master bus device to remove + */ +void w1_remove_master_device(struct w1_bus_master *bm) +{ + struct w1_master *dev, *found = NULL; + + list_for_each_entry(dev, &w1_masters, w1_master_entry) { + if (!dev->initialized) + continue; + + if (dev->bus_master->data == bm->data) { + found = dev; + break; + } + } + + if (!found) { + pr_err("Device doesn't exist.\n"); + return; + } + + __w1_remove_master_device(found); +} +EXPORT_SYMBOL(w1_remove_master_device); diff --git a/drivers/w1/w1_internal.h b/drivers/w1/w1_internal.h new file mode 100644 index 000000000..1623e2fdc --- /dev/null +++ b/drivers/w1/w1_internal.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __W1_H +#define __W1_H + +#include <linux/w1.h> + +#include <linux/completion.h> +#include <linux/mutex.h> + +#define W1_SLAVE_ACTIVE 0 +#define W1_SLAVE_DETACH 1 + +/** + * struct w1_async_cmd - execute callback from the w1_process kthread + * @async_entry: link entry + * @cb: callback function, must list_del and destroy this list before + * returning + * + * When inserted into the w1_master async_list, w1_process will execute + * the callback. Embed this into the structure with the command details. + */ +struct w1_async_cmd { + struct list_head async_entry; + void (*cb)(struct w1_master *dev, struct w1_async_cmd *async_cmd); +}; + +int w1_create_master_attributes(struct w1_master *master); +void w1_destroy_master_attributes(struct w1_master *master); +void w1_search(struct w1_master *dev, u8 search_type, + w1_slave_found_callback cb); +void w1_search_devices(struct w1_master *dev, u8 search_type, + w1_slave_found_callback cb); +/* call w1_unref_slave to release the reference counts w1_search_slave added */ +struct w1_slave *w1_search_slave(struct w1_reg_num *id); +/* + * decrements the reference on sl->master and sl, and cleans up if zero + * returns the reference count after it has been decremented + */ +int w1_unref_slave(struct w1_slave *sl); +void w1_slave_found(struct w1_master *dev, u64 rn); +void w1_search_process_cb(struct w1_master *dev, u8 search_type, + w1_slave_found_callback cb); +struct w1_slave *w1_slave_search_device(struct w1_master *dev, + struct w1_reg_num *rn); +struct w1_master *w1_search_master_id(u32 id); + +/* Disconnect and reconnect devices in the given family. Used for finding + * unclaimed devices after a family has been registered or releasing devices + * after a family has been unregistered. Set attach to 1 when a new family + * has just been registered, to 0 when it has been unregistered. + */ +void w1_reconnect_slaves(struct w1_family *f, int attach); +int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn); +/* 0 success, otherwise EBUSY */ +int w1_slave_detach(struct w1_slave *sl); + +void __w1_remove_master_device(struct w1_master *dev); + +void w1_family_put(struct w1_family *f); +void __w1_family_get(struct w1_family *f); +struct w1_family *w1_family_registered(u8 fid); + +extern struct device_driver w1_master_driver; +extern struct device w1_master_device; +extern int w1_max_slave_count; +extern int w1_max_slave_ttl; +extern struct list_head w1_masters; +extern struct mutex w1_mlock; +extern spinlock_t w1_flock; + +int w1_process_callbacks(struct w1_master *dev); +int w1_process(void *data); + +#endif /* __W1_H */ diff --git a/drivers/w1/w1_io.c b/drivers/w1/w1_io.c new file mode 100644 index 000000000..3516ce671 --- /dev/null +++ b/drivers/w1/w1_io.c @@ -0,0 +1,458 @@ +/* + * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <asm/io.h> + +#include <linux/delay.h> +#include <linux/moduleparam.h> +#include <linux/module.h> + +#include "w1_internal.h" + +static int w1_delay_parm = 1; +module_param_named(delay_coef, w1_delay_parm, int, 0); + +static int w1_disable_irqs = 0; +module_param_named(disable_irqs, w1_disable_irqs, int, 0); + +static u8 w1_crc8_table[] = { + 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, + 157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, + 35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, + 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, + 70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, + 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, + 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36, + 248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185, + 140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, + 17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80, + 175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, + 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115, + 202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139, + 87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22, + 233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168, + 116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53 +}; + +static void w1_delay(unsigned long tm) +{ + udelay(tm * w1_delay_parm); +} + +static void w1_write_bit(struct w1_master *dev, int bit); +static u8 w1_read_bit(struct w1_master *dev); + +/** + * w1_touch_bit() - Generates a write-0 or write-1 cycle and samples the level. + * @dev: the master device + * @bit: 0 - write a 0, 1 - write a 0 read the level + */ +u8 w1_touch_bit(struct w1_master *dev, int bit) +{ + if (dev->bus_master->touch_bit) + return dev->bus_master->touch_bit(dev->bus_master->data, bit); + else if (bit) + return w1_read_bit(dev); + else { + w1_write_bit(dev, 0); + return 0; + } +} +EXPORT_SYMBOL_GPL(w1_touch_bit); + +/** + * w1_write_bit() - Generates a write-0 or write-1 cycle. + * @dev: the master device + * @bit: bit to write + * + * Only call if dev->bus_master->touch_bit is NULL + */ +static void w1_write_bit(struct w1_master *dev, int bit) +{ + unsigned long flags = 0; + + if(w1_disable_irqs) local_irq_save(flags); + + if (bit) { + dev->bus_master->write_bit(dev->bus_master->data, 0); + w1_delay(6); + dev->bus_master->write_bit(dev->bus_master->data, 1); + w1_delay(64); + } else { + dev->bus_master->write_bit(dev->bus_master->data, 0); + w1_delay(60); + dev->bus_master->write_bit(dev->bus_master->data, 1); + w1_delay(10); + } + + if(w1_disable_irqs) local_irq_restore(flags); +} + +/** + * w1_pre_write() - pre-write operations + * @dev: the master device + * + * Pre-write operation, currently only supporting strong pullups. + * Program the hardware for a strong pullup, if one has been requested and + * the hardware supports it. + */ +static void w1_pre_write(struct w1_master *dev) +{ + if (dev->pullup_duration && + dev->enable_pullup && dev->bus_master->set_pullup) { + dev->bus_master->set_pullup(dev->bus_master->data, + dev->pullup_duration); + } +} + +/** + * w1_post_write() - post-write options + * @dev: the master device + * + * Post-write operation, currently only supporting strong pullups. + * If a strong pullup was requested, clear it if the hardware supports + * them, or execute the delay otherwise, in either case clear the request. + */ +static void w1_post_write(struct w1_master *dev) +{ + if (dev->pullup_duration) { + if (dev->enable_pullup && dev->bus_master->set_pullup) + dev->bus_master->set_pullup(dev->bus_master->data, 0); + else + msleep(dev->pullup_duration); + dev->pullup_duration = 0; + } +} + +/** + * w1_write_8() - Writes 8 bits. + * @dev: the master device + * @byte: the byte to write + */ +void w1_write_8(struct w1_master *dev, u8 byte) +{ + int i; + + if (dev->bus_master->write_byte) { + w1_pre_write(dev); + dev->bus_master->write_byte(dev->bus_master->data, byte); + } + else + for (i = 0; i < 8; ++i) { + if (i == 7) + w1_pre_write(dev); + w1_touch_bit(dev, (byte >> i) & 0x1); + } + w1_post_write(dev); +} +EXPORT_SYMBOL_GPL(w1_write_8); + + +/** + * w1_read_bit() - Generates a write-1 cycle and samples the level. + * @dev: the master device + * + * Only call if dev->bus_master->touch_bit is NULL + */ +static u8 w1_read_bit(struct w1_master *dev) +{ + int result; + unsigned long flags = 0; + + /* sample timing is critical here */ + local_irq_save(flags); + dev->bus_master->write_bit(dev->bus_master->data, 0); + w1_delay(6); + dev->bus_master->write_bit(dev->bus_master->data, 1); + w1_delay(9); + + result = dev->bus_master->read_bit(dev->bus_master->data); + local_irq_restore(flags); + + w1_delay(55); + + return result & 0x1; +} + +/** + * w1_triplet() - * Does a triplet - used for searching ROM addresses. + * @dev: the master device + * @bdir: the bit to write if both id_bit and comp_bit are 0 + * + * Return bits: + * bit 0 = id_bit + * bit 1 = comp_bit + * bit 2 = dir_taken + * + * If both bits 0 & 1 are set, the search should be restarted. + * + * Return: bit fields - see above + */ +u8 w1_triplet(struct w1_master *dev, int bdir) +{ + if (dev->bus_master->triplet) + return dev->bus_master->triplet(dev->bus_master->data, bdir); + else { + u8 id_bit = w1_touch_bit(dev, 1); + u8 comp_bit = w1_touch_bit(dev, 1); + u8 retval; + + if (id_bit && comp_bit) + return 0x03; /* error */ + + if (!id_bit && !comp_bit) { + /* Both bits are valid, take the direction given */ + retval = bdir ? 0x04 : 0; + } else { + /* Only one bit is valid, take that direction */ + bdir = id_bit; + retval = id_bit ? 0x05 : 0x02; + } + + if (dev->bus_master->touch_bit) + w1_touch_bit(dev, bdir); + else + w1_write_bit(dev, bdir); + return retval; + } +} +EXPORT_SYMBOL_GPL(w1_triplet); + +/** + * w1_read_8() - Reads 8 bits. + * @dev: the master device + * + * Return: the byte read + */ +u8 w1_read_8(struct w1_master *dev) +{ + int i; + u8 res = 0; + + if (dev->bus_master->read_byte) + res = dev->bus_master->read_byte(dev->bus_master->data); + else + for (i = 0; i < 8; ++i) + res |= (w1_touch_bit(dev,1) << i); + + return res; +} +EXPORT_SYMBOL_GPL(w1_read_8); + +/** + * w1_write_block() - Writes a series of bytes. + * @dev: the master device + * @buf: pointer to the data to write + * @len: the number of bytes to write + */ +void w1_write_block(struct w1_master *dev, const u8 *buf, int len) +{ + int i; + + if (dev->bus_master->write_block) { + w1_pre_write(dev); + dev->bus_master->write_block(dev->bus_master->data, buf, len); + } + else + for (i = 0; i < len; ++i) + w1_write_8(dev, buf[i]); /* calls w1_pre_write */ + w1_post_write(dev); +} +EXPORT_SYMBOL_GPL(w1_write_block); + +/** + * w1_touch_block() - Touches a series of bytes. + * @dev: the master device + * @buf: pointer to the data to write + * @len: the number of bytes to write + */ +void w1_touch_block(struct w1_master *dev, u8 *buf, int len) +{ + int i, j; + u8 tmp; + + for (i = 0; i < len; ++i) { + tmp = 0; + for (j = 0; j < 8; ++j) { + if (j == 7) + w1_pre_write(dev); + tmp |= w1_touch_bit(dev, (buf[i] >> j) & 0x1) << j; + } + + buf[i] = tmp; + } +} +EXPORT_SYMBOL_GPL(w1_touch_block); + +/** + * w1_read_block() - Reads a series of bytes. + * @dev: the master device + * @buf: pointer to the buffer to fill + * @len: the number of bytes to read + * Return: the number of bytes read + */ +u8 w1_read_block(struct w1_master *dev, u8 *buf, int len) +{ + int i; + u8 ret; + + if (dev->bus_master->read_block) + ret = dev->bus_master->read_block(dev->bus_master->data, buf, len); + else { + for (i = 0; i < len; ++i) + buf[i] = w1_read_8(dev); + ret = len; + } + + return ret; +} +EXPORT_SYMBOL_GPL(w1_read_block); + +/** + * w1_reset_bus() - Issues a reset bus sequence. + * @dev: the master device + * Return: 0=Device present, 1=No device present or error + */ +int w1_reset_bus(struct w1_master *dev) +{ + int result; + unsigned long flags = 0; + + if(w1_disable_irqs) local_irq_save(flags); + + if (dev->bus_master->reset_bus) + result = dev->bus_master->reset_bus(dev->bus_master->data) & 0x1; + else { + dev->bus_master->write_bit(dev->bus_master->data, 0); + /* minimum 480, max ? us + * be nice and sleep, except 18b20 spec lists 960us maximum, + * so until we can sleep with microsecond accuracy, spin. + * Feel free to come up with some other way to give up the + * cpu for such a short amount of time AND get it back in + * the maximum amount of time. + */ + w1_delay(500); + dev->bus_master->write_bit(dev->bus_master->data, 1); + w1_delay(70); + + result = dev->bus_master->read_bit(dev->bus_master->data) & 0x1; + /* minimum 70 (above) + 430 = 500 us + * There aren't any timing requirements between a reset and + * the following transactions. Sleeping is safe here. + */ + /* w1_delay(430); min required time */ + msleep(1); + } + + if(w1_disable_irqs) local_irq_restore(flags); + + return result; +} +EXPORT_SYMBOL_GPL(w1_reset_bus); + +u8 w1_calc_crc8(u8 * data, int len) +{ + u8 crc = 0; + + while (len--) + crc = w1_crc8_table[crc ^ *data++]; + + return crc; +} +EXPORT_SYMBOL_GPL(w1_calc_crc8); + +void w1_search_devices(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb) +{ + dev->attempts++; + if (dev->bus_master->search) + dev->bus_master->search(dev->bus_master->data, dev, + search_type, cb); + else + w1_search(dev, search_type, cb); +} + +/** + * w1_reset_select_slave() - reset and select a slave + * @sl: the slave to select + * + * Resets the bus and then selects the slave by sending either a skip rom + * or a rom match. A skip rom is issued if there is only one device + * registered on the bus. + * The w1 master lock must be held. + * + * Return: 0=success, anything else=error + */ +int w1_reset_select_slave(struct w1_slave *sl) +{ + if (w1_reset_bus(sl->master)) + return -1; + + if (sl->master->slave_count == 1) + w1_write_8(sl->master, W1_SKIP_ROM); + else { + u8 match[9] = {W1_MATCH_ROM, }; + u64 rn = le64_to_cpu(*((u64*)&sl->reg_num)); + + memcpy(&match[1], &rn, 8); + w1_write_block(sl->master, match, 9); + } + return 0; +} +EXPORT_SYMBOL_GPL(w1_reset_select_slave); + +/** + * w1_reset_resume_command() - resume instead of another match ROM + * @dev: the master device + * + * When the workflow with a slave amongst many requires several + * successive commands a reset between each, this function is similar + * to doing a reset then a match ROM for the last matched ROM. The + * advantage being that the matched ROM step is skipped in favor of the + * resume command. The slave must support the command of course. + * + * If the bus has only one slave, traditionnaly the match ROM is skipped + * and a "SKIP ROM" is done for efficiency. On multi-slave busses, this + * doesn't work of course, but the resume command is the next best thing. + * + * The w1 master lock must be held. + */ +int w1_reset_resume_command(struct w1_master *dev) +{ + if (w1_reset_bus(dev)) + return -1; + + w1_write_8(dev, dev->slave_count > 1 ? W1_RESUME_CMD : W1_SKIP_ROM); + return 0; +} +EXPORT_SYMBOL_GPL(w1_reset_resume_command); + +/** + * w1_next_pullup() - register for a strong pullup + * @dev: the master device + * @delay: time in milliseconds + * + * Put out a strong pull-up of the specified duration after the next write + * operation. Not all hardware supports strong pullups. Hardware that + * doesn't support strong pullups will sleep for the given time after the + * write operation without a strong pullup. This is a one shot request for + * the next write, specifying zero will clear a previous request. + * The w1 master lock must be held. + * + * Return: 0=success, anything else=error + */ +void w1_next_pullup(struct w1_master *dev, int delay) +{ + dev->pullup_duration = delay; +} +EXPORT_SYMBOL_GPL(w1_next_pullup); diff --git a/drivers/w1/w1_netlink.c b/drivers/w1/w1_netlink.c new file mode 100644 index 000000000..f2f099cae --- /dev/null +++ b/drivers/w1/w1_netlink.c @@ -0,0 +1,748 @@ +/* + * Copyright (c) 2003 Evgeniy Polyakov <zbr@ioremap.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/slab.h> +#include <linux/skbuff.h> +#include <linux/netlink.h> +#include <linux/connector.h> + +#include "w1_internal.h" +#include "w1_netlink.h" + +#if defined(CONFIG_W1_CON) && (defined(CONFIG_CONNECTOR) || (defined(CONFIG_CONNECTOR_MODULE) && defined(CONFIG_W1_MODULE))) + +/* Bundle together everything required to process a request in one memory + * allocation. + */ +struct w1_cb_block { + atomic_t refcnt; + u32 portid; /* Sending process port ID */ + /* maximum value for first_cn->len */ + u16 maxlen; + /* pointers to building up the reply message */ + struct cn_msg *first_cn; /* fixed once the structure is populated */ + struct cn_msg *cn; /* advances as cn_msg is appeneded */ + struct w1_netlink_msg *msg; /* advances as w1_netlink_msg is appened */ + struct w1_netlink_cmd *cmd; /* advances as cmds are appened */ + struct w1_netlink_msg *cur_msg; /* currently message being processed */ + /* copy of the original request follows */ + struct cn_msg request_cn; + /* followed by variable length: + * cn_msg, data (w1_netlink_msg and w1_netlink_cmd) + * one or more struct w1_cb_node + * reply first_cn, data (w1_netlink_msg and w1_netlink_cmd) + */ +}; +struct w1_cb_node { + struct w1_async_cmd async; + /* pointers within w1_cb_block and cn data */ + struct w1_cb_block *block; + struct w1_netlink_msg *msg; + struct w1_slave *sl; + struct w1_master *dev; +}; + +/** + * w1_reply_len() - calculate current reply length, compare to maxlen + * @block: block to calculate + * + * Calculates the current message length including possible multiple + * cn_msg and data, excludes the first sizeof(struct cn_msg). Direclty + * compariable to maxlen and usable to send the message. + */ +static u16 w1_reply_len(struct w1_cb_block *block) +{ + if (!block->cn) + return 0; + return (u8 *)block->cn - (u8 *)block->first_cn + block->cn->len; +} + +static void w1_unref_block(struct w1_cb_block *block) +{ + if (atomic_sub_return(1, &block->refcnt) == 0) { + u16 len = w1_reply_len(block); + if (len) { + cn_netlink_send_mult(block->first_cn, len, + block->portid, 0, GFP_KERNEL); + } + kfree(block); + } +} + +/** + * w1_reply_make_space() - send message if needed to make space + * @block: block to make space on + * @space: how many bytes requested + * + * Verify there is enough room left for the caller to add "space" bytes to the + * message, if there isn't send the message and reset. + */ +static void w1_reply_make_space(struct w1_cb_block *block, u16 space) +{ + u16 len = w1_reply_len(block); + if (len + space >= block->maxlen) { + cn_netlink_send_mult(block->first_cn, len, block->portid, 0, GFP_KERNEL); + block->first_cn->len = 0; + block->cn = NULL; + block->msg = NULL; + block->cmd = NULL; + } +} + +/* Early send when replies aren't bundled. */ +static void w1_netlink_check_send(struct w1_cb_block *block) +{ + if (!(block->request_cn.flags & W1_CN_BUNDLE) && block->cn) + w1_reply_make_space(block, block->maxlen); +} + +/** + * w1_netlink_setup_msg() - prepare to write block->msg + * @block: block to operate on + * @ack: determines if cn can be reused + * + * block->cn will be setup with the correct ack, advancing if needed + * block->cn->len does not include space for block->msg + * block->msg advances but remains uninitialized + */ +static void w1_netlink_setup_msg(struct w1_cb_block *block, u32 ack) +{ + if (block->cn && block->cn->ack == ack) { + block->msg = (struct w1_netlink_msg *)(block->cn->data + block->cn->len); + } else { + /* advance or set to data */ + if (block->cn) + block->cn = (struct cn_msg *)(block->cn->data + + block->cn->len); + else + block->cn = block->first_cn; + + memcpy(block->cn, &block->request_cn, sizeof(*block->cn)); + block->cn->len = 0; + block->cn->ack = ack; + block->msg = (struct w1_netlink_msg *)block->cn->data; + } +} + +/* Append cmd to msg, include cmd->data as well. This is because + * any following data goes with the command and in the case of a read is + * the results. + */ +static void w1_netlink_queue_cmd(struct w1_cb_block *block, + struct w1_netlink_cmd *cmd) +{ + u32 space; + w1_reply_make_space(block, sizeof(struct cn_msg) + + sizeof(struct w1_netlink_msg) + sizeof(*cmd) + cmd->len); + + /* There's a status message sent after each command, so no point + * in trying to bundle this cmd after an existing one, because + * there won't be one. Allocate and copy over a new cn_msg. + */ + w1_netlink_setup_msg(block, block->request_cn.seq + 1); + memcpy(block->msg, block->cur_msg, sizeof(*block->msg)); + block->cn->len += sizeof(*block->msg); + block->msg->len = 0; + block->cmd = (struct w1_netlink_cmd *)(block->msg->data); + + space = sizeof(*cmd) + cmd->len; + if (block->cmd != cmd) + memcpy(block->cmd, cmd, space); + block->cn->len += space; + block->msg->len += space; +} + +/* Append req_msg and req_cmd, no other commands and no data from req_cmd are + * copied. + */ +static void w1_netlink_queue_status(struct w1_cb_block *block, + struct w1_netlink_msg *req_msg, struct w1_netlink_cmd *req_cmd, + int error) +{ + u16 space = sizeof(struct cn_msg) + sizeof(*req_msg) + sizeof(*req_cmd); + w1_reply_make_space(block, space); + w1_netlink_setup_msg(block, block->request_cn.ack); + + memcpy(block->msg, req_msg, sizeof(*req_msg)); + block->cn->len += sizeof(*req_msg); + block->msg->len = 0; + block->msg->status = (u8)-error; + if (req_cmd) { + struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)block->msg->data; + memcpy(cmd, req_cmd, sizeof(*cmd)); + block->cn->len += sizeof(*cmd); + block->msg->len += sizeof(*cmd); + cmd->len = 0; + } + w1_netlink_check_send(block); +} + +/** + * w1_netlink_send_error() - sends the error message now + * @cn: original cn_msg + * @msg: original w1_netlink_msg + * @portid: where to send it + * @error: error status + * + * Use when a block isn't available to queue the message to and cn, msg + * might not be contiguous. + */ +static void w1_netlink_send_error(struct cn_msg *cn, struct w1_netlink_msg *msg, + int portid, int error) +{ + struct { + struct cn_msg cn; + struct w1_netlink_msg msg; + } packet; + memcpy(&packet.cn, cn, sizeof(packet.cn)); + memcpy(&packet.msg, msg, sizeof(packet.msg)); + packet.cn.len = sizeof(packet.msg); + packet.msg.len = 0; + packet.msg.status = (u8)-error; + cn_netlink_send(&packet.cn, portid, 0, GFP_KERNEL); +} + +/** + * w1_netlink_send() - sends w1 netlink notifications + * @dev: w1_master the even is associated with or for + * @msg: w1_netlink_msg message to be sent + * + * This are notifications generated from the kernel. + */ +void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *msg) +{ + struct { + struct cn_msg cn; + struct w1_netlink_msg msg; + } packet; + memset(&packet, 0, sizeof(packet)); + + packet.cn.id.idx = CN_W1_IDX; + packet.cn.id.val = CN_W1_VAL; + + packet.cn.seq = dev->seq++; + packet.cn.len = sizeof(*msg); + + memcpy(&packet.msg, msg, sizeof(*msg)); + packet.msg.len = 0; + + cn_netlink_send(&packet.cn, 0, 0, GFP_KERNEL); +} + +static void w1_send_slave(struct w1_master *dev, u64 rn) +{ + struct w1_cb_block *block = dev->priv; + struct w1_netlink_cmd *cache_cmd = block->cmd; + u64 *data; + + w1_reply_make_space(block, sizeof(*data)); + + /* Add cmd back if the packet was sent */ + if (!block->cmd) { + cache_cmd->len = 0; + w1_netlink_queue_cmd(block, cache_cmd); + } + + data = (u64 *)(block->cmd->data + block->cmd->len); + + *data = rn; + block->cn->len += sizeof(*data); + block->msg->len += sizeof(*data); + block->cmd->len += sizeof(*data); +} + +static void w1_found_send_slave(struct w1_master *dev, u64 rn) +{ + /* update kernel slave list */ + w1_slave_found(dev, rn); + + w1_send_slave(dev, rn); +} + +/* Get the current slave list, or search (with or without alarm) */ +static int w1_get_slaves(struct w1_master *dev, struct w1_netlink_cmd *req_cmd) +{ + struct w1_slave *sl; + + req_cmd->len = 0; + w1_netlink_queue_cmd(dev->priv, req_cmd); + + if (req_cmd->cmd == W1_CMD_LIST_SLAVES) { + u64 rn; + mutex_lock(&dev->list_mutex); + list_for_each_entry(sl, &dev->slist, w1_slave_entry) { + memcpy(&rn, &sl->reg_num, sizeof(rn)); + w1_send_slave(dev, rn); + } + mutex_unlock(&dev->list_mutex); + } else { + w1_search_process_cb(dev, req_cmd->cmd == W1_CMD_ALARM_SEARCH ? + W1_ALARM_SEARCH : W1_SEARCH, w1_found_send_slave); + } + + return 0; +} + +static int w1_process_command_io(struct w1_master *dev, + struct w1_netlink_cmd *cmd) +{ + int err = 0; + + switch (cmd->cmd) { + case W1_CMD_TOUCH: + w1_touch_block(dev, cmd->data, cmd->len); + w1_netlink_queue_cmd(dev->priv, cmd); + break; + case W1_CMD_READ: + w1_read_block(dev, cmd->data, cmd->len); + w1_netlink_queue_cmd(dev->priv, cmd); + break; + case W1_CMD_WRITE: + w1_write_block(dev, cmd->data, cmd->len); + break; + default: + err = -EINVAL; + break; + } + + return err; +} + +static int w1_process_command_addremove(struct w1_master *dev, + struct w1_netlink_cmd *cmd) +{ + struct w1_slave *sl; + int err = 0; + struct w1_reg_num *id; + + if (cmd->len != sizeof(*id)) + return -EINVAL; + + id = (struct w1_reg_num *)cmd->data; + + sl = w1_slave_search_device(dev, id); + switch (cmd->cmd) { + case W1_CMD_SLAVE_ADD: + if (sl) + err = -EINVAL; + else + err = w1_attach_slave_device(dev, id); + break; + case W1_CMD_SLAVE_REMOVE: + if (sl) + w1_slave_detach(sl); + else + err = -EINVAL; + break; + default: + err = -EINVAL; + break; + } + + return err; +} + +static int w1_process_command_master(struct w1_master *dev, + struct w1_netlink_cmd *req_cmd) +{ + int err = -EINVAL; + + /* drop bus_mutex for search (does it's own locking), and add/remove + * which doesn't use the bus + */ + switch (req_cmd->cmd) { + case W1_CMD_SEARCH: + case W1_CMD_ALARM_SEARCH: + case W1_CMD_LIST_SLAVES: + mutex_unlock(&dev->bus_mutex); + err = w1_get_slaves(dev, req_cmd); + mutex_lock(&dev->bus_mutex); + break; + case W1_CMD_READ: + case W1_CMD_WRITE: + case W1_CMD_TOUCH: + err = w1_process_command_io(dev, req_cmd); + break; + case W1_CMD_RESET: + err = w1_reset_bus(dev); + break; + case W1_CMD_SLAVE_ADD: + case W1_CMD_SLAVE_REMOVE: + mutex_unlock(&dev->bus_mutex); + mutex_lock(&dev->mutex); + err = w1_process_command_addremove(dev, req_cmd); + mutex_unlock(&dev->mutex); + mutex_lock(&dev->bus_mutex); + break; + default: + err = -EINVAL; + break; + } + + return err; +} + +static int w1_process_command_slave(struct w1_slave *sl, + struct w1_netlink_cmd *cmd) +{ + dev_dbg(&sl->master->dev, "%s: %02x.%012llx.%02x: cmd=%02x, len=%u.\n", + __func__, sl->reg_num.family, (unsigned long long)sl->reg_num.id, + sl->reg_num.crc, cmd->cmd, cmd->len); + + return w1_process_command_io(sl->master, cmd); +} + +static int w1_process_command_root(struct cn_msg *req_cn, u32 portid) +{ + struct w1_master *dev; + struct cn_msg *cn; + struct w1_netlink_msg *msg; + u32 *id; + + cn = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!cn) + return -ENOMEM; + + cn->id.idx = CN_W1_IDX; + cn->id.val = CN_W1_VAL; + + cn->seq = req_cn->seq; + cn->ack = req_cn->seq + 1; + cn->len = sizeof(struct w1_netlink_msg); + msg = (struct w1_netlink_msg *)cn->data; + + msg->type = W1_LIST_MASTERS; + msg->status = 0; + msg->len = 0; + id = (u32 *)msg->data; + + mutex_lock(&w1_mlock); + list_for_each_entry(dev, &w1_masters, w1_master_entry) { + if (cn->len + sizeof(*id) > PAGE_SIZE - sizeof(struct cn_msg)) { + cn_netlink_send(cn, portid, 0, GFP_KERNEL); + cn->len = sizeof(struct w1_netlink_msg); + msg->len = 0; + id = (u32 *)msg->data; + } + + *id = dev->id; + msg->len += sizeof(*id); + cn->len += sizeof(*id); + id++; + } + cn_netlink_send(cn, portid, 0, GFP_KERNEL); + mutex_unlock(&w1_mlock); + + kfree(cn); + return 0; +} + +static void w1_process_cb(struct w1_master *dev, struct w1_async_cmd *async_cmd) +{ + struct w1_cb_node *node = container_of(async_cmd, struct w1_cb_node, + async); + u16 mlen = node->msg->len; + u16 len; + int err = 0; + struct w1_slave *sl = node->sl; + struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)node->msg->data; + + mutex_lock(&dev->bus_mutex); + dev->priv = node->block; + if (sl && w1_reset_select_slave(sl)) + err = -ENODEV; + node->block->cur_msg = node->msg; + + while (mlen && !err) { + if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen) { + err = -E2BIG; + break; + } + + if (sl) + err = w1_process_command_slave(sl, cmd); + else + err = w1_process_command_master(dev, cmd); + w1_netlink_check_send(node->block); + + w1_netlink_queue_status(node->block, node->msg, cmd, err); + err = 0; + + len = sizeof(*cmd) + cmd->len; + cmd = (struct w1_netlink_cmd *)((u8 *)cmd + len); + mlen -= len; + } + + if (!cmd || err) + w1_netlink_queue_status(node->block, node->msg, cmd, err); + + /* ref taken in w1_search_slave or w1_search_master_id when building + * the block + */ + if (sl) + w1_unref_slave(sl); + else + atomic_dec(&dev->refcnt); + dev->priv = NULL; + mutex_unlock(&dev->bus_mutex); + + mutex_lock(&dev->list_mutex); + list_del(&async_cmd->async_entry); + mutex_unlock(&dev->list_mutex); + + w1_unref_block(node->block); +} + +static void w1_list_count_cmds(struct w1_netlink_msg *msg, int *cmd_count, + u16 *slave_len) +{ + struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)msg->data; + u16 mlen = msg->len; + u16 len; + int slave_list = 0; + while (mlen) { + if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen) + break; + + switch (cmd->cmd) { + case W1_CMD_SEARCH: + case W1_CMD_ALARM_SEARCH: + case W1_CMD_LIST_SLAVES: + ++slave_list; + } + ++*cmd_count; + len = sizeof(*cmd) + cmd->len; + cmd = (struct w1_netlink_cmd *)((u8 *)cmd + len); + mlen -= len; + } + + if (slave_list) { + struct w1_master *dev = w1_search_master_id(msg->id.mst.id); + if (dev) { + /* Bytes, and likely an overstimate, and if it isn't + * the results can still be split between packets. + */ + *slave_len += sizeof(struct w1_reg_num) * slave_list * + (dev->slave_count + dev->max_slave_count); + /* search incremented it */ + atomic_dec(&dev->refcnt); + } + } +} + +static void w1_cn_callback(struct cn_msg *cn, struct netlink_skb_parms *nsp) +{ + struct w1_netlink_msg *msg = (struct w1_netlink_msg *)(cn + 1); + struct w1_slave *sl; + struct w1_master *dev; + u16 msg_len; + u16 slave_len = 0; + int err = 0; + struct w1_cb_block *block = NULL; + struct w1_cb_node *node = NULL; + int node_count = 0; + int cmd_count = 0; + + /* If any unknown flag is set let the application know, that way + * applications can detect the absence of features in kernels that + * don't know about them. http://lwn.net/Articles/587527/ + */ + if (cn->flags & ~(W1_CN_BUNDLE)) { + w1_netlink_send_error(cn, msg, nsp->portid, -EINVAL); + return; + } + + /* Count the number of master or slave commands there are to allocate + * space for one cb_node each. + */ + msg_len = cn->len; + while (msg_len && !err) { + if (msg->len + sizeof(struct w1_netlink_msg) > msg_len) { + err = -E2BIG; + break; + } + + /* count messages for nodes and allocate any additional space + * required for slave lists + */ + if (msg->type == W1_MASTER_CMD || msg->type == W1_SLAVE_CMD) { + ++node_count; + w1_list_count_cmds(msg, &cmd_count, &slave_len); + } + + msg_len -= sizeof(struct w1_netlink_msg) + msg->len; + msg = (struct w1_netlink_msg *)(((u8 *)msg) + + sizeof(struct w1_netlink_msg) + msg->len); + } + msg = (struct w1_netlink_msg *)(cn + 1); + if (node_count) { + int size; + int reply_size = sizeof(*cn) + cn->len + slave_len; + if (cn->flags & W1_CN_BUNDLE) { + /* bundling duplicats some of the messages */ + reply_size += 2 * cmd_count * (sizeof(struct cn_msg) + + sizeof(struct w1_netlink_msg) + + sizeof(struct w1_netlink_cmd)); + } + reply_size = min(CONNECTOR_MAX_MSG_SIZE, reply_size); + + /* allocate space for the block, a copy of the original message, + * one node per cmd to point into the original message, + * space for replies which is the original message size plus + * space for any list slave data and status messages + * cn->len doesn't include itself which is part of the block + * */ + size = /* block + original message */ + sizeof(struct w1_cb_block) + sizeof(*cn) + cn->len + + /* space for nodes */ + node_count * sizeof(struct w1_cb_node) + + /* replies */ + sizeof(struct cn_msg) + reply_size; + block = kzalloc(size, GFP_KERNEL); + if (!block) { + /* if the system is already out of memory, + * (A) will this work, and (B) would it be better + * to not try? + */ + w1_netlink_send_error(cn, msg, nsp->portid, -ENOMEM); + return; + } + atomic_set(&block->refcnt, 1); + block->portid = nsp->portid; + memcpy(&block->request_cn, cn, sizeof(*cn) + cn->len); + node = (struct w1_cb_node *)(block->request_cn.data + cn->len); + + /* Sneeky, when not bundling, reply_size is the allocated space + * required for the reply, cn_msg isn't part of maxlen so + * it should be reply_size - sizeof(struct cn_msg), however + * when checking if there is enough space, w1_reply_make_space + * is called with the full message size including cn_msg, + * because it isn't known at that time if an additional cn_msg + * will need to be allocated. So an extra cn_msg is added + * above in "size". + */ + block->maxlen = reply_size; + block->first_cn = (struct cn_msg *)(node + node_count); + memset(block->first_cn, 0, sizeof(*block->first_cn)); + } + + msg_len = cn->len; + while (msg_len && !err) { + + dev = NULL; + sl = NULL; + + if (msg->len + sizeof(struct w1_netlink_msg) > msg_len) { + err = -E2BIG; + break; + } + + /* execute on this thread, no need to process later */ + if (msg->type == W1_LIST_MASTERS) { + err = w1_process_command_root(cn, nsp->portid); + goto out_cont; + } + + /* All following message types require additional data, + * check here before references are taken. + */ + if (!msg->len) { + err = -EPROTO; + goto out_cont; + } + + /* both search calls take references */ + if (msg->type == W1_MASTER_CMD) { + dev = w1_search_master_id(msg->id.mst.id); + } else if (msg->type == W1_SLAVE_CMD) { + sl = w1_search_slave((struct w1_reg_num *)msg->id.id); + if (sl) + dev = sl->master; + } else { + pr_notice("%s: cn: %x.%x, wrong type: %u, len: %u.\n", + __func__, cn->id.idx, cn->id.val, + msg->type, msg->len); + err = -EPROTO; + goto out_cont; + } + + if (!dev) { + err = -ENODEV; + goto out_cont; + } + + err = 0; + + atomic_inc(&block->refcnt); + node->async.cb = w1_process_cb; + node->block = block; + node->msg = (struct w1_netlink_msg *)((u8 *)&block->request_cn + + (size_t)((u8 *)msg - (u8 *)cn)); + node->sl = sl; + node->dev = dev; + + mutex_lock(&dev->list_mutex); + list_add_tail(&node->async.async_entry, &dev->async_list); + wake_up_process(dev->thread); + mutex_unlock(&dev->list_mutex); + ++node; + +out_cont: + /* Can't queue because that modifies block and another + * thread could be processing the messages by now and + * there isn't a lock, send directly. + */ + if (err) + w1_netlink_send_error(cn, msg, nsp->portid, err); + msg_len -= sizeof(struct w1_netlink_msg) + msg->len; + msg = (struct w1_netlink_msg *)(((u8 *)msg) + + sizeof(struct w1_netlink_msg) + msg->len); + + /* + * Let's allow requests for nonexisting devices. + */ + if (err == -ENODEV) + err = 0; + } + if (block) + w1_unref_block(block); +} + +int w1_init_netlink(void) +{ + struct cb_id w1_id = {.idx = CN_W1_IDX, .val = CN_W1_VAL}; + + return cn_add_callback(&w1_id, "w1", &w1_cn_callback); +} + +void w1_fini_netlink(void) +{ + struct cb_id w1_id = {.idx = CN_W1_IDX, .val = CN_W1_VAL}; + + cn_del_callback(&w1_id); +} +#else +void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *cn) +{ +} + +int w1_init_netlink(void) +{ + return 0; +} + +void w1_fini_netlink(void) +{ +} +#endif diff --git a/drivers/w1/w1_netlink.h b/drivers/w1/w1_netlink.h new file mode 100644 index 000000000..f876772c0 --- /dev/null +++ b/drivers/w1/w1_netlink.h @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2003 Evgeniy Polyakov <zbr@ioremap.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __W1_NETLINK_H +#define __W1_NETLINK_H + +#include <asm/types.h> +#include <linux/connector.h> + +#include "w1_internal.h" + +/** + * enum w1_cn_msg_flags - bitfield flags for struct cn_msg.flags + * + * @W1_CN_BUNDLE: Request bundling replies into fewer messagse. Be prepared + * to handle multiple struct cn_msg, struct w1_netlink_msg, and + * struct w1_netlink_cmd in one packet. + */ +enum w1_cn_msg_flags { + W1_CN_BUNDLE = 1, +}; + +/** + * enum w1_netlink_message_types - message type + * + * @W1_SLAVE_ADD: notification that a slave device was added + * @W1_SLAVE_REMOVE: notification that a slave device was removed + * @W1_MASTER_ADD: notification that a new bus master was added + * @W1_MASTER_REMOVE: notification that a bus masterwas removed + * @W1_MASTER_CMD: initiate operations on a specific master + * @W1_SLAVE_CMD: sends reset, selects the slave, then does a read/write/touch + * operation + * @W1_LIST_MASTERS: used to determine the bus master identifiers + */ +enum w1_netlink_message_types { + W1_SLAVE_ADD = 0, + W1_SLAVE_REMOVE, + W1_MASTER_ADD, + W1_MASTER_REMOVE, + W1_MASTER_CMD, + W1_SLAVE_CMD, + W1_LIST_MASTERS, +}; + +/** + * struct w1_netlink_msg - holds w1 message type, id, and result + * + * @type: one of enum w1_netlink_message_types + * @status: kernel feedback for success 0 or errno failure value + * @len: length of data following w1_netlink_msg + * @id: union holding bus master id (msg.id) and slave device id (id[8]). + * @id.id: Slave ID (8 bytes) + * @id.mst: bus master identification + * @id.mst.id: bus master ID + * @id.mst.res: bus master reserved + * @data: start address of any following data + * + * The base message structure for w1 messages over netlink. + * The netlink connector data sequence is, struct nlmsghdr, struct cn_msg, + * then one or more struct w1_netlink_msg (each with optional data). + */ +struct w1_netlink_msg +{ + __u8 type; + __u8 status; + __u16 len; + union { + __u8 id[8]; + struct w1_mst { + __u32 id; + __u32 res; + } mst; + } id; + __u8 data[0]; +}; + +/** + * enum w1_commands - commands available for master or slave operations + * + * @W1_CMD_READ: read len bytes + * @W1_CMD_WRITE: write len bytes + * @W1_CMD_SEARCH: initiate a standard search, returns only the slave + * devices found during that search + * @W1_CMD_ALARM_SEARCH: search for devices that are currently alarming + * @W1_CMD_TOUCH: Touches a series of bytes. + * @W1_CMD_RESET: sends a bus reset on the given master + * @W1_CMD_SLAVE_ADD: adds a slave to the given master, + * 8 byte slave id at data[0] + * @W1_CMD_SLAVE_REMOVE: removes a slave to the given master, + * 8 byte slave id at data[0] + * @W1_CMD_LIST_SLAVES: list of slaves registered on this master + * @W1_CMD_MAX: number of available commands + */ +enum w1_commands { + W1_CMD_READ = 0, + W1_CMD_WRITE, + W1_CMD_SEARCH, + W1_CMD_ALARM_SEARCH, + W1_CMD_TOUCH, + W1_CMD_RESET, + W1_CMD_SLAVE_ADD, + W1_CMD_SLAVE_REMOVE, + W1_CMD_LIST_SLAVES, + W1_CMD_MAX +}; + +/** + * struct w1_netlink_cmd - holds the command and data + * + * @cmd: one of enum w1_commands + * @res: reserved + * @len: length of data following w1_netlink_cmd + * @data: start address of any following data + * + * One or more struct w1_netlink_cmd is placed starting at w1_netlink_msg.data + * each with optional data. + */ +struct w1_netlink_cmd +{ + __u8 cmd; + __u8 res; + __u16 len; + __u8 data[0]; +}; + +#ifdef __KERNEL__ + +void w1_netlink_send(struct w1_master *, struct w1_netlink_msg *); +int w1_init_netlink(void); +void w1_fini_netlink(void); + +#endif /* __KERNEL__ */ +#endif /* __W1_NETLINK_H */ |