diff options
Diffstat (limited to 'drivers/w1')
34 files changed, 8990 insertions, 0 deletions
diff --git a/drivers/w1/Kconfig b/drivers/w1/Kconfig new file mode 100644 index 00000000..fd2c7bd9 --- /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 00000000..6bb0b549 --- /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 00000000..979d6eed --- /dev/null +++ b/drivers/w1/masters/Kconfig @@ -0,0 +1,69 @@ +# +# 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 && EXPERIMENTAL + 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 W1 && ARCH_MXC + help + Say Y here to enable MXC 1-wire host + +config W1_MASTER_DS1WM + tristate "Maxim DS1WM 1-wire busmaster" + depends on W1 && GENERIC_HARDIRQS + 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 GENERIC_GPIO + 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 SOC_OMAP2430 || ARCH_OMAP3 + 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 00000000..c5a3e96f --- /dev/null +++ b/drivers/w1/masters/Makefile @@ -0,0 +1,12 @@ +# +# 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 00000000..a0c8965c --- /dev/null +++ b/drivers/w1/masters/ds1wm.c @@ -0,0 +1,594 @@ +/* + * 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/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 "../w1.h" +#include "../w1_int.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; + int bus_shift; /* # of shifts to calc register offsets */ + 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) +{ + __raw_writeb(val, ds1wm_data->map + (reg << ds1wm_data->bus_shift)); +} + +static inline u8 ds1wm_read_register(struct ds1wm_data *ds1wm_data, u32 reg) +{ + return __raw_readb(ds1wm_data->map + (reg << ds1wm_data->bus_shift)); +} + + +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 ds1wm_driver_data *plat = ds1wm_data->pdev->dev.platform_data; + + if (ds1wm_data->cell->enable) + ds1wm_data->cell->enable(ds1wm_data->pdev); + + divisor = ds1wm_find_divisor(plat->clock_rate); + dev_dbg(&ds1wm_data->pdev->dev, + "found divisor 0x%x for clock %d\n", divisor, plat->clock_rate); + if (divisor == 0) { + dev_err(&ds1wm_data->pdev->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; + } + + if (ds1wm_reset(ds1wm_data)) { + 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 begining 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) { + 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); + 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; + + if (!pdev) + return -ENODEV; + + ds1wm_data = kzalloc(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) { + ret = -ENXIO; + goto err0; + } + ds1wm_data->map = ioremap(res->start, resource_size(res)); + if (!ds1wm_data->map) { + ret = -ENOMEM; + goto err0; + } + + /* calculate bus shift from mem resource */ + ds1wm_data->bus_shift = resource_size(res) >> 3; + + ds1wm_data->pdev = pdev; + ds1wm_data->cell = mfd_get_cell(pdev); + if (!ds1wm_data->cell) { + ret = -ENODEV; + goto err1; + } + plat = pdev->dev.platform_data; + if (!plat) { + ret = -ENODEV; + goto err1; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + ret = -ENXIO; + goto err1; + } + 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; + + 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); + + ret = request_irq(ds1wm_data->irq, ds1wm_isr, + IRQF_DISABLED | IRQF_SHARED, "ds1wm", ds1wm_data); + if (ret) + goto err1; + + ds1wm_up(ds1wm_data); + + ds1wm_master.data = (void *)ds1wm_data; + + ret = w1_add_master_device(&ds1wm_master); + if (ret) + goto err2; + + return 0; + +err2: + ds1wm_down(ds1wm_data); + free_irq(ds1wm_data->irq, ds1wm_data); +err1: + iounmap(ds1wm_data->map); +err0: + kfree(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); + free_irq(ds1wm_data->irq, ds1wm_data); + iounmap(ds1wm_data->map); + kfree(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) +{ + printk("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 00000000..e5f74416 --- /dev/null +++ b/drivers/w1/masters/ds2482.c @@ -0,0 +1,523 @@ +/** + * 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 "../w1.h" +#include "../w1_int.h" + +/** + * 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 +#define DS2482_REG_CFG_SPU 0x04 +#define DS2482_REG_CFG_PPM 0x02 +#define DS2482_REG_CFG_APU 0x01 + + +/** + * 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 + + +static int ds2482_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int ds2482_remove(struct i2c_client *client); + + +/** + * Driver data (common to all clients) + */ +static const struct i2c_device_id ds2482_id[] = { + { "ds2482", 0 }, + { } +}; + +static struct i2c_driver ds2482_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ds2482", + }, + .probe = ds2482_probe, + .remove = ds2482_remove, + .id_table = ds2482_id, +}; + +/* + * 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; +}; + + +/** + * 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) + printk(KERN_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, + 0xF0); + } + + mutex_unlock(&pdev->access_lock); + + 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, 0xF0); + + 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; + + 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; +} + +static int __init sensors_ds2482_init(void) +{ + return i2c_add_driver(&ds2482_driver); +} + +static void __exit sensors_ds2482_exit(void) +{ + i2c_del_driver(&ds2482_driver); +} + +MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>"); +MODULE_DESCRIPTION("DS2482 driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_ds2482_init); +module_exit(sensors_ds2482_exit); diff --git a/drivers/w1/masters/ds2490.c b/drivers/w1/masters/ds2490.c new file mode 100644 index 00000000..02bf7bf7 --- /dev/null +++ b/drivers/w1/masters/ds2490.c @@ -0,0 +1,1028 @@ +/* + * dscore.c + * + * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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 "../w1_int.h" +#include "../w1.h" + +/* 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 + +/* 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; + + 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 struct usb_device_id ds_id_table [] = { + { USB_DEVICE(0x04fa, 0x2490) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, ds_id_table); + +static int ds_probe(struct usb_interface *, const struct usb_device_id *); +static void ds_disconnect(struct usb_interface *); + +static int ds_send_control(struct ds_device *, u16, u16); +static int ds_send_control_cmd(struct ds_device *, u16, u16); + +static LIST_HEAD(ds_devices); +static DEFINE_MUTEX(ds_mutex); + +static struct usb_driver ds_driver = { + .name = "DS9490R", + .probe = ds_probe, + .disconnect = ds_disconnect, + .id_table = ds_id_table, +}; + +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, 0x40, value, index, NULL, 0, 1000); + if (err < 0) { + printk(KERN_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, 0x40, value, index, NULL, 0, 1000); + if (err < 0) { + printk(KERN_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, 0x40, value, index, NULL, 0, 1000); + if (err < 0) { + printk(KERN_ERR "Failed to send control message %x.%x: err=%d.\n", + value, index, err); + return err; + } + + return err; +} + +static int ds_recv_status_nodump(struct ds_device *dev, struct ds_status *st, + unsigned char *buf, int size) +{ + int count, err; + + memset(st, 0, sizeof(*st)); + + count = 0; + err = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, dev->ep[EP_STATUS]), buf, size, &count, 100); + if (err < 0) { + printk(KERN_ERR "Failed to read 1-wire data from 0x%x: err=%d.\n", dev->ep[EP_STATUS], err); + return err; + } + + if (count >= sizeof(*st)) + memcpy(st, buf, sizeof(*st)); + + return count; +} + +static inline void ds_print_msg(unsigned char *buf, unsigned char *str, int off) +{ + printk(KERN_INFO "%45s: %8x\n", str, buf[off]); +} + +static void ds_dump_status(struct ds_device *dev, unsigned char *buf, int count) +{ + int i; + + printk(KERN_INFO "0x%x: count=%d, status: ", dev->ep[EP_STATUS], count); + for (i=0; i<count; ++i) + printk("%02x ", buf[i]); + printk(KERN_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) + printk(KERN_INFO "NRS: Reset no presence or ...\n"); + if (buf[i] & RR_SH) + printk(KERN_INFO "SH: short on reset or set path\n"); + if (buf[i] & RR_APP) + printk(KERN_INFO "APP: alarming presence on reset\n"); + if (buf[i] & RR_VPP) + printk(KERN_INFO "VPP: 12V expected not seen\n"); + if (buf[i] & RR_CMP) + printk(KERN_INFO "CMP: compare error\n"); + if (buf[i] & RR_CRC) + printk(KERN_INFO "CRC: CRC error detected\n"); + if (buf[i] & RR_RDP) + printk(KERN_INFO "RDP: redirected page\n"); + if (buf[i] & RR_EOS) + printk(KERN_INFO "EOS: end of search error\n"); + } +} + +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)) + printk(KERN_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)) + printk(KERN_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; + struct ds_status st; + + /* 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) { + u8 buf[0x20]; + int count; + + printk(KERN_INFO "Clearing ep0x%x.\n", dev->ep[EP_DATA_IN]); + usb_clear_halt(dev->udev, usb_rcvbulkpipe(dev->udev, dev->ep[EP_DATA_IN])); + + count = ds_recv_status_nodump(dev, &st, buf, sizeof(buf)); + ds_dump_status(dev, buf, count); + 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) { + printk(KERN_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; + u8 buf[0x20]; + + 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_nodump(dev, &st, buf, sizeof(buf)); + 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) +{ + u8 buf[0x20]; + int err, count = 0; + + do { + err = ds_recv_status_nodump(dev, st, buf, sizeof(buf)); +#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 ", buf[i]); + printk("\n"); + } +#endif + } while (!(buf[0x08] & ST_IDLE) && !(err < 0) && ++count < 100); + + if (err >= 16 && st->status & ST_EPOF) { + printk(KERN_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, 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; + u8 rbyte; + + 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, &rbyte, sizeof(rbyte)); + if (err < 0) + return err; + + return !(byte == rbyte); +} + +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); +} + +#if 0 + +static int ds_search(struct ds_device *dev, u64 init, u64 *buf, u8 id_number, int conditional_search) +{ + int err; + u16 value, index; + struct ds_status st; + + memset(buf, 0, sizeof(buf)); + + err = ds_send_data(ds_dev, (unsigned char *)&init, 8); + if (err) + return err; + + ds_wait_status(ds_dev, &st); + + value = COMM_SEARCH_ACCESS | COMM_IM | COMM_SM | COMM_F | COMM_RTS; + index = (conditional_search ? 0xEC : 0xF0) | (id_number << 8); + err = ds_send_control(ds_dev, value, index); + if (err) + return err; + + ds_wait_status(ds_dev, &st); + + err = ds_recv_data(ds_dev, (unsigned char *)buf, 8*id_number); + if (err < 0) + return err; + + return err/8; +} + +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) +{ + u8 ret; + struct ds_device *dev = data; + + if (ds_touch_bit(dev, bit, &ret)) + return 0; + + return ret; +} + +#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; + u8 bit = 0; + + err = ds_touch_bit(dev, 1, &bit); + if (err) + return 0; + + return bit & 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; + u8 byte = 0; + + err = ds_read_byte(dev, &byte); + if (err) + return 0; + + return byte; +} + +static void ds9490r_write_block(void *data, const u8 *buf, int len) +{ + struct ds_device *dev = data; + + ds_write_block(dev, (u8 *)buf, len); +} + +static u8 ds9490r_read_block(void *data, u8 *buf, int len) +{ + struct ds_device *dev = data; + int err; + + err = ds_read_block(dev, buf, len); + if (err < 0) + return 0; + + return len; +} + +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; + + 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; + + dev = kmalloc(sizeof(struct ds_device), GFP_KERNEL); + if (!dev) { + printk(KERN_INFO "Failed to allocate new DS9490R structure.\n"); + return -ENOMEM; + } + dev->spu_sleep = 0; + dev->spu_bit = 0; + 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_set_interface(dev->udev, intf->altsetting[0].desc.bInterfaceNumber, 3); + if (err) { + printk(KERN_ERR "Failed to set alternative setting 3 for %d interface: err=%d.\n", + intf->altsetting[0].desc.bInterfaceNumber, err); + goto err_out_clear; + } + + err = usb_reset_configuration(dev->udev); + if (err) { + printk(KERN_ERR "Failed to reset configuration: err=%d.\n", err); + goto err_out_clear; + } + + iface_desc = &intf->altsetting[0]; + if (iface_desc->desc.bNumEndpoints != NUM_EP-1) { + printk(KERN_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 int ds_init(void) +{ + int err; + + err = usb_register(&ds_driver); + if (err) { + printk(KERN_INFO "Failed to register DS9490R USB device: err=%d.\n", err); + return err; + } + + return 0; +} + +static void ds_fini(void) +{ + usb_deregister(&ds_driver); +} + +module_init(ds_init); +module_exit(ds_fini); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>"); +MODULE_DESCRIPTION("DS2490 USB <-> W1 bus master driver (DS9490*)"); diff --git a/drivers/w1/masters/matrox_w1.c b/drivers/w1/masters/matrox_w1.c new file mode 100644 index 00000000..1550431c --- /dev/null +++ b/drivers/w1/masters/matrox_w1.c @@ -0,0 +1,247 @@ +/* + * matrox_w1.c + * + * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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 <asm/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 "../w1.h" +#include "../w1_int.h" +#include "../w1_log.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>"); +MODULE_DESCRIPTION("Driver for transport(Dallas 1-wire prtocol) over VGA DDC(matrox gpio)."); + +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 int __devinit matrox_w1_probe(struct pci_dev *, const struct pci_device_id *); +static void __devexit matrox_w1_remove(struct pci_dev *); + +static struct pci_driver matrox_w1_pci_driver = { + .name = "matrox_w1", + .id_table = matrox_w1_tbl, + .probe = matrox_w1_probe, + .remove = __devexit_p(matrox_w1_remove), +}; + +/* + * 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; +}; + +static u8 matrox_w1_read_ddc_bit(void *); +static void matrox_w1_write_ddc_bit(void *, u8); + +/* + * 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 __devinit matrox_w1_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct matrox_device *dev; + int err; + + assert(pdev != NULL); + assert(ent != NULL); + + 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 __devexit matrox_w1_remove(struct pci_dev *pdev) +{ + struct matrox_device *dev = pci_get_drvdata(pdev); + + assert(dev != NULL); + + if (dev->found) { + w1_remove_master_device(dev->bus_master); + iounmap(dev->virt_addr); + } + kfree(dev); +} + +static int __init matrox_w1_init(void) +{ + return pci_register_driver(&matrox_w1_pci_driver); +} + +static void __exit matrox_w1_fini(void) +{ + pci_unregister_driver(&matrox_w1_pci_driver); +} + +module_init(matrox_w1_init); +module_exit(matrox_w1_fini); diff --git a/drivers/w1/masters/mxc_w1.c b/drivers/w1/masters/mxc_w1.c new file mode 100644 index 00000000..d90e905e --- /dev/null +++ b/drivers/w1/masters/mxc_w1.c @@ -0,0 +1,387 @@ +/* + * 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. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <mach/hardware.h> + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_log.h" + +/* According to the mx27 Datasheet the reset procedure should take up to about + * 1350us. We set the timeout to 500*100us = 50ms for sure */ +#define MXC_W1_RESET_TIMEOUT 500 + +/* + * MXC W1 Register offsets + */ +#define MXC_W1_CONTROL 0x00 +#define MXC_W1_TIME_DIVIDER 0x02 +#define MXC_W1_RESET 0x04 +#define MXC_W1_COMMAND 0x06 +#define MXC_W1_TXRX 0x08 +#define MXC_W1_INTERRUPT 0x0A +#define MXC_W1_INTERRUPT_EN 0x0C + +static DECLARE_COMPLETION(transmit_done); + +struct mxc_w1_device { + void __iomem *regs; + unsigned int clkdiv; + 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) +{ + u8 reg_val; + unsigned int timeout_cnt = 0; + struct mxc_w1_device *dev = data; + + __raw_writeb(0x80, (dev->regs + MXC_W1_CONTROL)); + + while (1) { + reg_val = __raw_readb(dev->regs + MXC_W1_CONTROL); + + if (((reg_val >> 7) & 0x1) == 0 || + timeout_cnt > MXC_W1_RESET_TIMEOUT) + break; + else + timeout_cnt++; + + udelay(100); + } + return (reg_val >> 7) & 0x1; +} + +/* + * 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 *mdev = data; + void __iomem *ctrl_addr = mdev->regs + MXC_W1_CONTROL; + unsigned int timeout_cnt = 400; /* Takes max. 120us according to + * datasheet. + */ + + __raw_writeb((1 << (5 - bit)), ctrl_addr); + + while (timeout_cnt--) { + if (!((__raw_readb(ctrl_addr) >> (5 - bit)) & 0x1)) + break; + + udelay(1); + } + + return ((__raw_readb(ctrl_addr)) >> 3) & 0x1; +} + +static void mxc_w1_ds2_write_byte(void *data, u8 byte) +{ + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + INIT_COMPLETION(transmit_done); + __raw_writeb(byte, (dev->regs + MXC_W1_TXRX)); + __raw_writeb(0x10, (dev->regs + MXC_W1_INTERRUPT_EN)); + wait_for_completion(&transmit_done); +} +static u8 mxc_w1_ds2_read_byte(void *data) +{ + volatile u8 reg_val; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + mxc_w1_ds2_write_byte(data, 0xFF); + reg_val = __raw_readb((dev->regs + MXC_W1_TXRX)); + return reg_val; +} +static u8 mxc_w1_read_byte(void *data) +{ + volatile u8 reg_val; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + reg_val = __raw_readb((dev->regs + MXC_W1_TXRX)); + return reg_val; +} +static irqreturn_t w1_interrupt_handler(int irq, void *data) +{ + u8 reg_val; + irqreturn_t ret = IRQ_NONE; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + reg_val = __raw_readb((dev->regs + MXC_W1_INTERRUPT)); + if ((reg_val & 0x10)) { + complete(&transmit_done); + reg_val = __raw_readb((dev->regs + MXC_W1_TXRX)); + ret = IRQ_HANDLED; + } + return ret; +} +void search_ROM_accelerator(void *data, struct w1_master *master, u8 search_type, + w1_slave_found_callback cb) +{ + u64 rn[2], last_rn[2], rn2[2]; + u64 rn1, rom_id, temp, temp1; + int i, j, z, w, last_zero, loop; + u8 bit, reg_val, bit2; + u8 byte, byte1; + int disc, prev_disc, last_disc; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + last_rn[0] = 0; + last_rn[1] = 0; + rom_id = 0; + prev_disc = 0; + loop = 0; + disc = -1; + last_disc = 0; + last_zero = 0; + while (!last_zero) { + /* + * 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. + */ + if (mxc_w1_ds2_reset_bus(data)) { + pr_debug("No devices present on the wire.\n"); + break; + } + rn[0] = 0; + rn[1] = 0; + __raw_writeb(0x80, (dev->regs + MXC_W1_CONTROL)); + mdelay(1); + mxc_w1_ds2_write_byte(data, 0xF0); + __raw_writeb(0x02, (dev->regs + MXC_W1_COMMAND)); + memcpy(rn2, last_rn, 16); + z = 0; + w = 0; + for (i = 0; i < 16; i++) { + reg_val = rn2[z] >> (8 * w); + mxc_w1_ds2_write_byte(data, reg_val); + reg_val = mxc_w1_read_byte(data); + if ((reg_val & 0x3) == 0x3) { + pr_debug("Device is Not Responding\n"); + break; + } + for (j = 0; j < 8; j += 2) { + byte = 0xFF; + byte1 = 1; + byte ^= byte1 << j; + bit = (reg_val >> j) & 0x1; + bit2 = (reg_val >> j); + if (bit) { + prev_disc = disc; + disc = 8 * i + j; + reg_val &= byte; + } + } + rn1 = 0; + rn1 = reg_val; + rn[z] |= rn1 << (8 * w); + w++; + if (i == 7) { + z++; + w = 0; + } + } + if ((disc == -1) || (disc == prev_disc)) + last_zero = 1; + if (disc == last_disc) + disc = prev_disc; + z = 0; + rom_id = 0; + for (i = 0, j = 1; i < 64; i++) { + temp = 0; + temp = (rn[z] >> j) & 0x1; + rom_id |= (temp << i); + j += 2; + if (i == 31) { + z++; + j = 1; + } + + } + if (disc > 63) { + last_rn[0] = rn[0]; + temp1 = rn[1]; + loop = disc % 64; + temp = 1; + temp1 |= (temp << (loop + 1)) - 1; + temp1 |= (temp << (loop + 1)); + last_rn[1] = temp1; + + } else { + last_rn[1] = 0; + temp1 = rn[0]; + temp = 1; + temp1 |= (temp << (loop + 1)) - 1; + temp1 |= (temp << (loop + 1)); + last_rn[0] = temp1; + } + last_disc = disc; + cb(master, rom_id); + } +} + +static int __devinit mxc_w1_probe(struct platform_device *pdev) +{ + struct mxc_w1_device *mdev; + struct mxc_w1_config *data = + (struct mxc_w1_config *)pdev->dev.platform_data; + struct resource *res; + int irq = 0; + int err = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + mdev = kzalloc(sizeof(struct mxc_w1_device), GFP_KERNEL); + if (!mdev) + return -ENOMEM; + + mdev->clk = clk_get(&pdev->dev, "owire"); + if (!mdev->clk) { + err = -ENODEV; + goto failed_clk; + } + + mdev->clkdiv = (clk_get_rate(mdev->clk) / 1000000) - 1; + + res = request_mem_region(res->start, resource_size(res), + "mxc_w1"); + if (!res) { + err = -EBUSY; + goto failed_req; + } + + mdev->regs = ioremap(res->start, resource_size(res)); + if (!mdev->regs) { + printk(KERN_ERR "Cannot map frame buffer registers\n"); + goto failed_ioremap; + } + + clk_enable(mdev->clk); + __raw_writeb(mdev->clkdiv, 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; + if (data->search_rom_accelerator) { + mdev->bus_master.write_byte = &mxc_w1_ds2_write_byte; + mdev->bus_master.read_byte = &mxc_w1_ds2_read_byte; + mdev->bus_master.search = &search_ROM_accelerator; + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + err = -ENOENT; + goto failed_irq; + } + err = request_irq(irq, w1_interrupt_handler, 0, "mxc_w1", mdev); + if (err) { + pr_debug("OWire:request_irq(%d) returned error %d\n", + irq, err); + goto failed_irq; + } + } + + err = w1_add_master_device(&mdev->bus_master); + + if (err) + goto failed_add; + + platform_set_drvdata(pdev, mdev); + return 0; + +failed_add: + if (irq) + free_irq(irq, mdev); +failed_irq: + iounmap(mdev->regs); +failed_ioremap: + release_mem_region(res->start, resource_size(res)); +failed_req: + clk_put(mdev->clk); +failed_clk: + kfree(mdev); + return err; +} + +/* + * disassociate the w1 device from the driver + */ +static int __devexit mxc_w1_remove(struct platform_device *pdev) +{ + struct mxc_w1_device *mdev = platform_get_drvdata(pdev); + struct resource *res; + struct mxc_w1_config *data = + (struct mxc_w1_config *)pdev->dev.platform_data; + int irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + w1_remove_master_device(&mdev->bus_master); + + iounmap(mdev->regs); + release_mem_region(res->start, resource_size(res)); + + irq = platform_get_irq(pdev, 0); + if ((irq >= 0) && (data->search_rom_accelerator)) + free_irq(irq, mdev); + + clk_disable(mdev->clk); + clk_put(mdev->clk); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver mxc_w1_driver = { + .driver = { + .name = "mxc_w1", + }, + .probe = mxc_w1_probe, + .remove = mxc_w1_remove, +}; + +static int __init mxc_w1_init(void) +{ + return platform_driver_register(&mxc_w1_driver); +} + +static void mxc_w1_exit(void) +{ + platform_driver_unregister(&mxc_w1_driver); +} + +module_init(mxc_w1_init); +module_exit(mxc_w1_exit); + +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 00000000..5ef385bf --- /dev/null +++ b/drivers/w1/masters/omap_hdq.c @@ -0,0 +1,728 @@ +/* + * drivers/w1/masters/omap_hdq.c + * + * Copyright (C) 2007 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/clk.h> +#include <linux/io.h> +#include <linux/sched.h> + +#include <asm/irq.h> +#include <mach/hardware.h> + +#include "../w1.h" +#include "../w1_int.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_INTERRUPTMASK (1<<6) +#define OMAP_HDQ_CTRL_STATUS_CLOCKENABLE (1<<5) +#define OMAP_HDQ_CTRL_STATUS_GO (1<<4) +#define OMAP_HDQ_CTRL_STATUS_INITIALIZATION (1<<2) +#define OMAP_HDQ_CTRL_STATUS_DIR (1<<1) +#define OMAP_HDQ_CTRL_STATUS_MODE (1<<0) +#define OMAP_HDQ_INT_STATUS 0x10 +#define OMAP_HDQ_INT_STATUS_TXCOMPLETE (1<<2) +#define OMAP_HDQ_INT_STATUS_RXCOMPLETE (1<<1) +#define OMAP_HDQ_INT_STATUS_TIMEOUT (1<<0) +#define OMAP_HDQ_SYSCONFIG 0x14 +#define OMAP_HDQ_SYSCONFIG_SOFTRESET (1<<1) +#define OMAP_HDQ_SYSCONFIG_AUTOIDLE (1<<0) +#define OMAP_HDQ_SYSSTATUS 0x18 +#define OMAP_HDQ_SYSSTATUS_RESETDONE (1<<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; + +struct hdq_data { + struct device *dev; + void __iomem *hdq_base; + /* lock status update */ + struct mutex hdq_mutex; + int hdq_usecount; + struct clk *hdq_ick; + struct clk *hdq_fck; + 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; +}; + +static int __devinit omap_hdq_probe(struct platform_device *pdev); +static int omap_hdq_remove(struct platform_device *pdev); + +static struct platform_driver omap_hdq_driver = { + .probe = omap_hdq_probe, + .remove = omap_hdq_remove, + .driver = { + .name = "omap_hdq", + }, +}; + +static u8 omap_w1_read_byte(void *_hdq); +static void omap_w1_write_byte(void *_hdq, u8 byte); +static u8 omap_w1_reset_bus(void *_hdq); +static void omap_w1_search_bus(void *_hdq, struct w1_master *master_dev, + u8 search_type, w1_slave_found_callback slave_found); + + +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, + .search = omap_w1_search_bus, +}; + +/* HDQ register I/O routines */ +static inline u8 hdq_reg_in(struct hdq_data *hdq_data, u32 offset) +{ + return __raw_readb(hdq_data->hdq_base + offset); +} + +static inline void hdq_reg_out(struct hdq_data *hdq_data, u32 offset, u8 val) +{ + __raw_writeb(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_readb(hdq_data->hdq_base + offset) & ~mask) + | (val & mask); + __raw_writeb(new_val, hdq_data->hdq_base + offset); + + return new_val; +} + +/* + * 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"); + 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", *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", 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", 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; +} + +/* HDQ Mode: always return success */ +static u8 omap_w1_reset_bus(void *_hdq) +{ + return 0; +} + +/* W1 search callback function */ +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 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_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", + tmp_status); + 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", 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; + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT; + + 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. It + * triggers another interrupt before we + * sleep. So we have to wait for RXCOMPLETE bit. + */ + while (!(hdq_data->hdq_irqstatus + & OMAP_HDQ_INT_STATUS_RXCOMPLETE) + && time_before(jiffies, timeout)) { + schedule_timeout_uninterruptible(1); + } + 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 0; + +} + +/* Enable clocks and set the controller to HDQ 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) { + if (clk_enable(hdq_data->hdq_ick)) { + dev_dbg(hdq_data->dev, "Can not enable ick\n"); + ret = -ENODEV; + goto clk_err; + } + if (clk_enable(hdq_data->hdq_fck)) { + dev_dbg(hdq_data->dev, "Can not enable fck\n"); + clk_disable(hdq_data->hdq_ick); + ret = -ENODEV; + goto clk_err; + } + + /* make sure HDQ 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 mode & enable clocks */ + hdq_reg_out(hdq_data, OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE | + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK); + hdq_reg_out(hdq_data, OMAP_HDQ_SYSCONFIG, + OMAP_HDQ_SYSCONFIG_AUTOIDLE); + hdq_reg_in(hdq_data, OMAP_HDQ_INT_STATUS); + } + } + } + +clk_err: + clk_put(hdq_data->hdq_ick); + clk_put(hdq_data->hdq_fck); +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; + + 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) { + clk_disable(hdq_data->hdq_ick); + clk_disable(hdq_data->hdq_fck); + } + } + mutex_unlock(&hdq_data->hdq_mutex); + + return ret; +} + +/* 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; + + 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; + } + + /* 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); + + 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); + } + + return; +} + +static int __devinit omap_hdq_probe(struct platform_device *pdev) +{ + struct hdq_data *hdq_data; + struct resource *res; + int ret, irq; + u8 rev; + + hdq_data = kmalloc(sizeof(*hdq_data), GFP_KERNEL); + if (!hdq_data) { + dev_dbg(&pdev->dev, "unable to allocate memory\n"); + ret = -ENOMEM; + goto err_kmalloc; + } + + hdq_data->dev = &pdev->dev; + platform_set_drvdata(pdev, hdq_data); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_dbg(&pdev->dev, "unable to get resource\n"); + ret = -ENXIO; + goto err_resource; + } + + hdq_data->hdq_base = ioremap(res->start, SZ_4K); + if (!hdq_data->hdq_base) { + dev_dbg(&pdev->dev, "ioremap failed\n"); + ret = -EINVAL; + goto err_ioremap; + } + + /* get interface & functional clock objects */ + hdq_data->hdq_ick = clk_get(&pdev->dev, "ick"); + if (IS_ERR(hdq_data->hdq_ick)) { + dev_dbg(&pdev->dev, "Can't get HDQ ick clock object\n"); + ret = PTR_ERR(hdq_data->hdq_ick); + goto err_ick; + } + + hdq_data->hdq_fck = clk_get(&pdev->dev, "fck"); + if (IS_ERR(hdq_data->hdq_fck)) { + dev_dbg(&pdev->dev, "Can't get HDQ fck clock object\n"); + ret = PTR_ERR(hdq_data->hdq_fck); + goto err_fck; + } + + hdq_data->hdq_usecount = 0; + mutex_init(&hdq_data->hdq_mutex); + + if (clk_enable(hdq_data->hdq_ick)) { + dev_dbg(&pdev->dev, "Can not enable ick\n"); + ret = -ENODEV; + goto err_intfclk; + } + + if (clk_enable(hdq_data->hdq_fck)) { + dev_dbg(&pdev->dev, "Can not enable fck\n"); + ret = -ENODEV; + goto err_fnclk; + } + + 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) { + ret = -ENXIO; + goto err_irq; + } + + ret = request_irq(irq, hdq_isr, IRQF_DISABLED, "omap_hdq", hdq_data); + if (ret < 0) { + dev_dbg(&pdev->dev, "could not request irq\n"); + goto err_irq; + } + + omap_hdq_break(hdq_data); + + /* don't clock the HDQ until it is needed */ + clk_disable(hdq_data->hdq_ick); + clk_disable(hdq_data->hdq_fck); + + 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_w1: +err_irq: + clk_disable(hdq_data->hdq_fck); + +err_fnclk: + clk_disable(hdq_data->hdq_ick); + +err_intfclk: + clk_put(hdq_data->hdq_fck); + +err_fck: + clk_put(hdq_data->hdq_ick); + +err_ick: + iounmap(hdq_data->hdq_base); + +err_ioremap: +err_resource: + platform_set_drvdata(pdev, NULL); + kfree(hdq_data); + +err_kmalloc: + 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 */ + clk_put(hdq_data->hdq_ick); + clk_put(hdq_data->hdq_fck); + free_irq(INT_24XX_HDQ_IRQ, hdq_data); + platform_set_drvdata(pdev, NULL); + iounmap(hdq_data->hdq_base); + kfree(hdq_data); + + return 0; +} + +static int __init +omap_hdq_init(void) +{ + return platform_driver_register(&omap_hdq_driver); +} +module_init(omap_hdq_init); + +static void __exit +omap_hdq_exit(void) +{ + platform_driver_unregister(&omap_hdq_driver); +} +module_exit(omap_hdq_exit); + +module_param(w1_id, int, S_IRUSR); +MODULE_PARM_DESC(w1_id, "1-wire id for the slave detection"); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("HDQ 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 00000000..fcbe7421 --- /dev/null +++ b/drivers/w1/masters/w1-gpio.c @@ -0,0 +1,160 @@ +/* + * 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 "../w1.h" +#include "../w1_int.h" + +#include <asm/gpio.h> + +static void w1_gpio_write_bit_dir(void *data, u8 bit) +{ + struct w1_gpio_platform_data *pdata = data; + + if (bit) + gpio_direction_input(pdata->pin); + else + gpio_direction_output(pdata->pin, 0); +} + +static void w1_gpio_write_bit_val(void *data, u8 bit) +{ + struct w1_gpio_platform_data *pdata = data; + + gpio_set_value(pdata->pin, bit); +} + +static u8 w1_gpio_read_bit(void *data) +{ + struct w1_gpio_platform_data *pdata = data; + + return gpio_get_value(pdata->pin) ? 1 : 0; +} + +static int __init w1_gpio_probe(struct platform_device *pdev) +{ + struct w1_bus_master *master; + struct w1_gpio_platform_data *pdata = pdev->dev.platform_data; + int err; + + if (!pdata) + return -ENXIO; + + master = kzalloc(sizeof(struct w1_bus_master), GFP_KERNEL); + if (!master) + return -ENOMEM; + + err = gpio_request(pdata->pin, "w1"); + if (err) + goto free_master; + + master->data = pdata; + master->read_bit = w1_gpio_read_bit; + + if (pdata->is_open_drain) { + gpio_direction_output(pdata->pin, 1); + master->write_bit = w1_gpio_write_bit_val; + } else { + gpio_direction_input(pdata->pin); + master->write_bit = w1_gpio_write_bit_dir; + } + + err = w1_add_master_device(master); + if (err) + goto free_gpio; + + if (pdata->enable_external_pullup) + pdata->enable_external_pullup(1); + + platform_set_drvdata(pdev, master); + + return 0; + + free_gpio: + gpio_free(pdata->pin); + free_master: + kfree(master); + + return err; +} + +static int __exit w1_gpio_remove(struct platform_device *pdev) +{ + struct w1_bus_master *master = platform_get_drvdata(pdev); + struct w1_gpio_platform_data *pdata = pdev->dev.platform_data; + + if (pdata->enable_external_pullup) + pdata->enable_external_pullup(0); + + w1_remove_master_device(master); + gpio_free(pdata->pin); + kfree(master); + + return 0; +} + +#ifdef CONFIG_PM + +static int w1_gpio_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct w1_gpio_platform_data *pdata = pdev->dev.platform_data; + + if (pdata->enable_external_pullup) + pdata->enable_external_pullup(0); + + return 0; +} + +static int w1_gpio_resume(struct platform_device *pdev) +{ + struct w1_gpio_platform_data *pdata = pdev->dev.platform_data; + + if (pdata->enable_external_pullup) + pdata->enable_external_pullup(1); + + return 0; +} + +#else +#define w1_gpio_suspend NULL +#define w1_gpio_resume NULL +#endif + +static struct platform_driver w1_gpio_driver = { + .driver = { + .name = "w1-gpio", + .owner = THIS_MODULE, + }, + .remove = __exit_p(w1_gpio_remove), + .suspend = w1_gpio_suspend, + .resume = w1_gpio_resume, +}; + +static int __init w1_gpio_init(void) +{ + return platform_driver_probe(&w1_gpio_driver, w1_gpio_probe); +} + +static void __exit w1_gpio_exit(void) +{ + platform_driver_unregister(&w1_gpio_driver); +} + +module_init(w1_gpio_init); +module_exit(w1_gpio_exit); + +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 00000000..d0cb01b4 --- /dev/null +++ b/drivers/w1/slaves/Kconfig @@ -0,0 +1,91 @@ +# +# 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_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_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_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_DS2760 + tristate "Dallas 2760 battery monitor chip (HP iPAQ & others)" + depends on W1 + help + If you enable this you will have the DS2760 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_DS2780 + tristate "Dallas 2780 battery monitor chip" + depends on W1 + 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_BQ27000 + tristate "BQ27000 slave support" + depends on W1 + help + Say Y here if you want to use a hdq + bq27000 slave support. + +endmenu diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile new file mode 100644 index 00000000..1f31e9fb --- /dev/null +++ b/drivers/w1/slaves/Makefile @@ -0,0 +1,13 @@ +# +# 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_DS2408) += w1_ds2408.o +obj-$(CONFIG_W1_SLAVE_DS2423) += w1_ds2423.o +obj-$(CONFIG_W1_SLAVE_DS2431) += w1_ds2431.o +obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o +obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o +obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o +obj-$(CONFIG_W1_SLAVE_BQ27000) += w1_bq27000.o diff --git a/drivers/w1/slaves/w1_bq27000.c b/drivers/w1/slaves/w1_bq27000.c new file mode 100644 index 00000000..8f4c91f6 --- /dev/null +++ b/drivers/w1/slaves/w1_bq27000.c @@ -0,0 +1,123 @@ +/* + * drivers/w1/slaves/w1_bq27000.c + * + * Copyright (C) 2007 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/device.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +#define HDQ_CMD_READ (0) +#define HDQ_CMD_WRITE (1<<7) + +static int F_ID; + +void w1_bq27000_write(struct device *dev, u8 buf, u8 reg) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (!dev) { + pr_info("Could not obtain slave dev ptr\n"); + return; + } + + w1_write_8(sl->master, HDQ_CMD_WRITE | reg); + w1_write_8(sl->master, buf); +} +EXPORT_SYMBOL(w1_bq27000_write); + +int w1_bq27000_read(struct device *dev, u8 reg) +{ + u8 val; + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (!dev) + return 0; + + w1_write_8(sl->master, HDQ_CMD_READ | reg); + val = w1_read_8(sl->master); + + return val; +} +EXPORT_SYMBOL(w1_bq27000_read); + +static int w1_bq27000_add_slave(struct w1_slave *sl) +{ + int ret; + int id = 1; + struct platform_device *pdev; + + pdev = platform_device_alloc("bq27000-battery", id); + if (!pdev) { + ret = -ENOMEM; + return ret; + } + pdev->dev.parent = &sl->dev; + + ret = platform_device_add(pdev); + if (ret) + goto pdev_add_failed; + + dev_set_drvdata(&sl->dev, pdev); + + goto success; + +pdev_add_failed: + platform_device_unregister(pdev); +success: + return ret; +} + +static void w1_bq27000_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_bq27000_fops = { + .add_slave = w1_bq27000_add_slave, + .remove_slave = w1_bq27000_remove_slave, +}; + +static struct w1_family w1_bq27000_family = { + .fid = 1, + .fops = &w1_bq27000_fops, +}; + +static int __init w1_bq27000_init(void) +{ + if (F_ID) + w1_bq27000_family.fid = F_ID; + + return w1_register_family(&w1_bq27000_family); +} + +static void __exit w1_bq27000_exit(void) +{ + w1_unregister_family(&w1_bq27000_family); +} + + +module_init(w1_bq27000_init); +module_exit(w1_bq27000_exit); + +module_param(F_ID, int, S_IRUSR); +MODULE_PARM_DESC(F_ID, "1-wire slave FID for BQ device"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments Ltd"); +MODULE_DESCRIPTION("HDQ/1-wire slave driver bq27000 battery monitor chip"); diff --git a/drivers/w1/slaves/w1_ds2408.c b/drivers/w1/slaves/w1_ds2408.c new file mode 100644 index 00000000..c3778189 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2408.c @@ -0,0 +1,402 @@ +/* + * w1_ds2408.c - w1 family 29 (DS2408) driver + * + * Copyright (c) 2010 Jean-Francois Dagenais <dagenaisj@sonatest.com> + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jean-Francois Dagenais <dagenaisj@sonatest.com>"); +MODULE_DESCRIPTION("w1 family 29 driver for DS2408 8 Pin IO"); + + +#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->mutex); + dev_dbg(&sl->dev, "mutex locked"); + + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->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->mutex); + dev_dbg(&sl->dev, "mutex unlocked"); + return 1; +} + +static ssize_t w1_f29_read_state( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + dev_dbg(&kobj_to_w1_slave(kobj)->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), W1_F29_REG_LOGIG_STATE, buf); +} + +static ssize_t w1_f29_read_output( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + dev_dbg(&kobj_to_w1_slave(kobj)->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_OUTPUT_LATCH_STATE, buf); +} + +static ssize_t w1_f29_read_activity( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + dev_dbg(&kobj_to_w1_slave(kobj)->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_ACTIVITY_LATCH_STATE, buf); +} + +static ssize_t w1_f29_read_cond_search_mask( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + dev_dbg(&kobj_to_w1_slave(kobj)->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_COND_SEARCH_SELECT_MASK, buf); +} + +static ssize_t w1_f29_read_cond_search_polarity( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_COND_SEARCH_POL_SELECT, buf); +} + +static ssize_t w1_f29_read_status_control( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_CONTROL_AND_STATUS, buf); +} + + + + +static ssize_t w1_f29_write_output( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u8 w1_buf[3]; + u8 readBack; + unsigned int retries = W1_F29_RETRIES; + + if (count != 1 || off != 0) + return -EFAULT; + + dev_dbg(&sl->dev, "locking mutex for write_output"); + mutex_lock(&sl->master->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); + /* 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; + + if (readBack != 0xAA) { + /* try again, the slave is ready for a command */ + continue; + } + + /* 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) { + /* success! */ + mutex_unlock(&sl->master->mutex); + dev_dbg(&sl->dev, + "mutex unlocked, retries:%d", retries); + return 1; + } + } +error: + mutex_unlock(&sl->master->mutex); + dev_dbg(&sl->dev, "mutex unlocked in error, retries:%d", retries); + + return -EIO; +} + + +/** + * Writing to the activity file resets the activity latches. + */ +static ssize_t w1_f29_write_activity( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + unsigned int retries = W1_F29_RETRIES; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->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->mutex); + return 1; + } + if (w1_reset_resume_command(sl->master)) + goto error; + } + +error: + mutex_unlock(&sl->master->mutex); + return -EIO; +} + +static ssize_t w1_f29_write_status_control( + struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, + loff_t off, + size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u8 w1_buf[4]; + unsigned int retries = W1_F29_RETRIES; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->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->mutex); + return 1; + } + } +error: + mutex_unlock(&sl->master->mutex); + + return -EIO; +} + + + +#define NB_SYSFS_BIN_FILES 6 +static struct bin_attribute w1_f29_sysfs_bin_files[NB_SYSFS_BIN_FILES] = { + { + .attr = { + .name = "state", + .mode = S_IRUGO, + }, + .size = 1, + .read = w1_f29_read_state, + }, + { + .attr = { + .name = "output", + .mode = S_IRUGO | S_IWUSR | S_IWGRP, + }, + .size = 1, + .read = w1_f29_read_output, + .write = w1_f29_write_output, + }, + { + .attr = { + .name = "activity", + .mode = S_IRUGO, + }, + .size = 1, + .read = w1_f29_read_activity, + .write = w1_f29_write_activity, + }, + { + .attr = { + .name = "cond_search_mask", + .mode = S_IRUGO, + }, + .size = 1, + .read = w1_f29_read_cond_search_mask, + .write = 0, + }, + { + .attr = { + .name = "cond_search_polarity", + .mode = S_IRUGO, + }, + .size = 1, + .read = w1_f29_read_cond_search_polarity, + .write = 0, + }, + { + .attr = { + .name = "status_control", + .mode = S_IRUGO | S_IWUSR | S_IWGRP, + }, + .size = 1, + .read = w1_f29_read_status_control, + .write = w1_f29_write_status_control, + } +}; + +static int w1_f29_add_slave(struct w1_slave *sl) +{ + int err = 0; + int i; + + for (i = 0; i < NB_SYSFS_BIN_FILES && !err; ++i) + err = sysfs_create_bin_file( + &sl->dev.kobj, + &(w1_f29_sysfs_bin_files[i])); + if (err) + while (--i >= 0) + sysfs_remove_bin_file(&sl->dev.kobj, + &(w1_f29_sysfs_bin_files[i])); + return err; +} + +static void w1_f29_remove_slave(struct w1_slave *sl) +{ + int i; + for (i = NB_SYSFS_BIN_FILES; i <= 0; --i) + sysfs_remove_bin_file(&sl->dev.kobj, + &(w1_f29_sysfs_bin_files[i])); +} + +static struct w1_family_ops w1_f29_fops = { + .add_slave = w1_f29_add_slave, + .remove_slave = w1_f29_remove_slave, +}; + +static struct w1_family w1_family_29 = { + .fid = W1_FAMILY_DS2408, + .fops = &w1_f29_fops, +}; + +static int __init w1_f29_init(void) +{ + return w1_register_family(&w1_family_29); +} + +static void __exit w1_f29_exit(void) +{ + w1_unregister_family(&w1_family_29); +} + +module_init(w1_f29_init); +module_exit(w1_f29_exit); diff --git a/drivers/w1/slaves/w1_ds2423.c b/drivers/w1/slaves/w1_ds2423.c new file mode 100644 index 00000000..7a7dbe50 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2423.c @@ -0,0 +1,166 @@ +/* + * 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 "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +#define CRC16_VALID 0xb001 +#define CRC16_INIT 0 + +#define COUNTER_COUNT 4 +#define READ_BYTE_COUNT 42 + +static ssize_t w1_counter_read(struct device *device, + struct device_attribute *attr, char *buf); + +static struct device_attribute w1_counter_attr = + __ATTR(w1_slave, S_IRUGO, w1_counter_read, NULL); + +static ssize_t w1_counter_read(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->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->mutex); + return PAGE_SIZE - c; +} + +static int w1_f1d_add_slave(struct w1_slave *sl) +{ + return device_create_file(&sl->dev, &w1_counter_attr); +} + +static void w1_f1d_remove_slave(struct w1_slave *sl) +{ + device_remove_file(&sl->dev, &w1_counter_attr); +} + +static struct w1_family_ops w1_f1d_fops = { + .add_slave = w1_f1d_add_slave, + .remove_slave = w1_f1d_remove_slave, +}; + +static struct w1_family w1_family_1d = { + .fid = W1_COUNTER_DS2423, + .fops = &w1_f1d_fops, +}; + +static int __init w1_f1d_init(void) +{ + return w1_register_family(&w1_family_1d); +} + +static void __exit w1_f1d_exit(void) +{ + w1_unregister_family(&w1_family_1d); +} + +module_init(w1_f1d_init); +module_exit(w1_f1d_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mika Laitio <lamikr@pilppa.org>"); +MODULE_DESCRIPTION("w1 family 1d driver for DS2423, 4 counters and 4kb ram"); diff --git a/drivers/w1/slaves/w1_ds2431.c b/drivers/w1/slaves/w1_ds2431.c new file mode 100644 index 00000000..84e2410a --- /dev/null +++ b/drivers/w1/slaves/w1_ds2431.c @@ -0,0 +1,312 @@ +/* + * 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 "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +#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 w1_f2d_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_f2d_fix_count(off, count, W1_F2D_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->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->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 w1_f2d_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_f2d_fix_count(off, count, W1_F2D_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_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->mutex); + + return count; +} + +static struct bin_attribute w1_f2d_bin_attr = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = W1_F2D_EEPROM_SIZE, + .read = w1_f2d_read_bin, + .write = w1_f2d_write_bin, +}; + +static int w1_f2d_add_slave(struct w1_slave *sl) +{ + return sysfs_create_bin_file(&sl->dev.kobj, &w1_f2d_bin_attr); +} + +static void w1_f2d_remove_slave(struct w1_slave *sl) +{ + sysfs_remove_bin_file(&sl->dev.kobj, &w1_f2d_bin_attr); +} + +static struct w1_family_ops w1_f2d_fops = { + .add_slave = w1_f2d_add_slave, + .remove_slave = w1_f2d_remove_slave, +}; + +static struct w1_family w1_family_2d = { + .fid = W1_EEPROM_DS2431, + .fops = &w1_f2d_fops, +}; + +static int __init w1_f2d_init(void) +{ + return w1_register_family(&w1_family_2d); +} + +static void __exit w1_f2d_fini(void) +{ + w1_unregister_family(&w1_family_2d); +} + +module_init(w1_f2d_init); +module_exit(w1_f2d_fini); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Bernhard Weirich <bernhard.weirich@riedel.net>"); +MODULE_DESCRIPTION("w1 family 2d driver for DS2431, 1kb EEPROM"); diff --git a/drivers/w1/slaves/w1_ds2433.c b/drivers/w1/slaves/w1_ds2433.c new file mode 100644 index 00000000..0f7b8f9c --- /dev/null +++ b/drivers/w1/slaves/w1_ds2433.c @@ -0,0 +1,322 @@ +/* + * 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 "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>"); +MODULE_DESCRIPTION("w1 family 23 driver for DS2433, 4kb EEPROM"); + +#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 w1_f23_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); +#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->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->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 w1_f23_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, 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->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->mutex); + + return count; +} + +static struct bin_attribute w1_f23_bin_attr = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = W1_EEPROM_SIZE, + .read = w1_f23_read_bin, + .write = w1_f23_write_bin, +}; + +static int w1_f23_add_slave(struct w1_slave *sl) +{ + int err; +#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 */ + + err = sysfs_create_bin_file(&sl->dev.kobj, &w1_f23_bin_attr); + +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + if (err) + kfree(data); +#endif /* CONFIG_W1_SLAVE_DS2433_CRC */ + + return err; +} + +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 */ + sysfs_remove_bin_file(&sl->dev.kobj, &w1_f23_bin_attr); +} + +static struct w1_family_ops w1_f23_fops = { + .add_slave = w1_f23_add_slave, + .remove_slave = w1_f23_remove_slave, +}; + +static struct w1_family w1_family_23 = { + .fid = W1_EEPROM_DS2433, + .fops = &w1_f23_fops, +}; + +static int __init w1_f23_init(void) +{ + return w1_register_family(&w1_family_23); +} + +static void __exit w1_f23_fini(void) +{ + w1_unregister_family(&w1_family_23); +} + +module_init(w1_f23_init); +module_exit(w1_f23_fini); diff --git a/drivers/w1/slaves/w1_ds2760.c b/drivers/w1/slaves/w1_ds2760.c new file mode 100644 index 00000000..483d4518 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2760.c @@ -0,0 +1,240 @@ +/* + * 1-Wire implementation for the ds2760 chip + * + * Copyright © 2004-2005, Szabolcs Gyurko <szabolcs.gyurko@tlt.hu> + * + * 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/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/gfp.h> + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" +#include "w1_ds2760.h" + +static int w1_ds2760_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 (!dev) + return 0; + + mutex_lock(&sl->master->mutex); + + if (addr > DS2760_DATA_SIZE || addr < 0) { + count = 0; + goto out; + } + if (addr + count > DS2760_DATA_SIZE) + count = DS2760_DATA_SIZE - addr; + + if (!w1_reset_select_slave(sl)) { + if (!io) { + w1_write_8(sl->master, W1_DS2760_READ_DATA); + w1_write_8(sl->master, addr); + count = w1_read_block(sl->master, buf, count); + } else { + w1_write_8(sl->master, W1_DS2760_WRITE_DATA); + w1_write_8(sl->master, addr); + w1_write_block(sl->master, buf, count); + /* XXX w1_write_block returns void, not n_written */ + } + } + +out: + mutex_unlock(&sl->master->mutex); + + return count; +} + +int w1_ds2760_read(struct device *dev, char *buf, int addr, size_t count) +{ + return w1_ds2760_io(dev, buf, addr, count, 0); +} + +int w1_ds2760_write(struct device *dev, char *buf, int addr, size_t count) +{ + return w1_ds2760_io(dev, buf, addr, count, 1); +} + +static int w1_ds2760_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->mutex); + + if (w1_reset_select_slave(sl) == 0) { + w1_write_8(sl->master, cmd); + w1_write_8(sl->master, addr); + } + + mutex_unlock(&sl->master->mutex); + return 0; +} + +int w1_ds2760_store_eeprom(struct device *dev, int addr) +{ + return w1_ds2760_eeprom_cmd(dev, addr, W1_DS2760_COPY_DATA); +} + +int w1_ds2760_recall_eeprom(struct device *dev, int addr) +{ + return w1_ds2760_eeprom_cmd(dev, addr, W1_DS2760_RECALL_DATA); +} + +static ssize_t w1_ds2760_read_bin(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_ds2760_read(dev, buf, off, count); +} + +static struct bin_attribute w1_ds2760_bin_attr = { + .attr = { + .name = "w1_slave", + .mode = S_IRUGO, + }, + .size = DS2760_DATA_SIZE, + .read = w1_ds2760_read_bin, +}; + +static DEFINE_IDR(bat_idr); +static DEFINE_MUTEX(bat_idr_lock); + +static int new_bat_id(void) +{ + int ret; + + while (1) { + int id; + + ret = idr_pre_get(&bat_idr, GFP_KERNEL); + if (ret == 0) + return -ENOMEM; + + mutex_lock(&bat_idr_lock); + ret = idr_get_new(&bat_idr, NULL, &id); + mutex_unlock(&bat_idr_lock); + + if (ret == 0) { + ret = id & MAX_ID_MASK; + break; + } else if (ret == -EAGAIN) { + continue; + } else { + break; + } + } + + return ret; +} + +static void release_bat_id(int id) +{ + mutex_lock(&bat_idr_lock); + idr_remove(&bat_idr, id); + mutex_unlock(&bat_idr_lock); +} + +static int w1_ds2760_add_slave(struct w1_slave *sl) +{ + int ret; + int id; + struct platform_device *pdev; + + id = new_bat_id(); + if (id < 0) { + ret = id; + goto noid; + } + + pdev = platform_device_alloc("ds2760-battery", id); + if (!pdev) { + ret = -ENOMEM; + goto pdev_alloc_failed; + } + pdev->dev.parent = &sl->dev; + + ret = platform_device_add(pdev); + if (ret) + goto pdev_add_failed; + + ret = sysfs_create_bin_file(&sl->dev.kobj, &w1_ds2760_bin_attr); + if (ret) + goto bin_attr_failed; + + dev_set_drvdata(&sl->dev, pdev); + + goto success; + +bin_attr_failed: +pdev_add_failed: + platform_device_unregister(pdev); +pdev_alloc_failed: + release_bat_id(id); +noid: +success: + return ret; +} + +static void w1_ds2760_remove_slave(struct w1_slave *sl) +{ + struct platform_device *pdev = dev_get_drvdata(&sl->dev); + int id = pdev->id; + + platform_device_unregister(pdev); + release_bat_id(id); + sysfs_remove_bin_file(&sl->dev.kobj, &w1_ds2760_bin_attr); +} + +static struct w1_family_ops w1_ds2760_fops = { + .add_slave = w1_ds2760_add_slave, + .remove_slave = w1_ds2760_remove_slave, +}; + +static struct w1_family w1_ds2760_family = { + .fid = W1_FAMILY_DS2760, + .fops = &w1_ds2760_fops, +}; + +static int __init w1_ds2760_init(void) +{ + printk(KERN_INFO "1-Wire driver for the DS2760 battery monitor " + " chip - (c) 2004-2005, Szabolcs Gyurko\n"); + idr_init(&bat_idr); + return w1_register_family(&w1_ds2760_family); +} + +static void __exit w1_ds2760_exit(void) +{ + w1_unregister_family(&w1_ds2760_family); + idr_destroy(&bat_idr); +} + +EXPORT_SYMBOL(w1_ds2760_read); +EXPORT_SYMBOL(w1_ds2760_write); +EXPORT_SYMBOL(w1_ds2760_store_eeprom); +EXPORT_SYMBOL(w1_ds2760_recall_eeprom); + +module_init(w1_ds2760_init); +module_exit(w1_ds2760_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>"); +MODULE_DESCRIPTION("1-wire Driver Dallas 2760 battery monitor chip"); diff --git a/drivers/w1/slaves/w1_ds2760.h b/drivers/w1/slaves/w1_ds2760.h new file mode 100644 index 00000000..58e77414 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2760.h @@ -0,0 +1,57 @@ +/* + * 1-Wire implementation for the ds2760 chip + * + * Copyright © 2004-2005, Szabolcs Gyurko <szabolcs.gyurko@tlt.hu> + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + * + */ + +#ifndef __w1_ds2760_h__ +#define __w1_ds2760_h__ + +/* Known commands to the DS2760 chip */ +#define W1_DS2760_SWAP 0xAA +#define W1_DS2760_READ_DATA 0x69 +#define W1_DS2760_WRITE_DATA 0x6C +#define W1_DS2760_COPY_DATA 0x48 +#define W1_DS2760_RECALL_DATA 0xB8 +#define W1_DS2760_LOCK 0x6A + +/* Number of valid register addresses */ +#define DS2760_DATA_SIZE 0x40 + +#define DS2760_PROTECTION_REG 0x00 +#define DS2760_STATUS_REG 0x01 + #define DS2760_STATUS_IE (1 << 2) + #define DS2760_STATUS_SWEN (1 << 3) + #define DS2760_STATUS_RNAOP (1 << 4) + #define DS2760_STATUS_PMOD (1 << 5) +#define DS2760_EEPROM_REG 0x07 +#define DS2760_SPECIAL_FEATURE_REG 0x08 +#define DS2760_VOLTAGE_MSB 0x0c +#define DS2760_VOLTAGE_LSB 0x0d +#define DS2760_CURRENT_MSB 0x0e +#define DS2760_CURRENT_LSB 0x0f +#define DS2760_CURRENT_ACCUM_MSB 0x10 +#define DS2760_CURRENT_ACCUM_LSB 0x11 +#define DS2760_TEMP_MSB 0x18 +#define DS2760_TEMP_LSB 0x19 +#define DS2760_EEPROM_BLOCK0 0x20 +#define DS2760_ACTIVE_FULL 0x20 +#define DS2760_EEPROM_BLOCK1 0x30 +#define DS2760_STATUS_WRITE_REG 0x31 +#define DS2760_RATED_CAPACITY 0x32 +#define DS2760_CURRENT_OFFSET_BIAS 0x33 +#define DS2760_ACTIVE_EMPTY 0x3b + +extern int w1_ds2760_read(struct device *dev, char *buf, int addr, + size_t count); +extern int w1_ds2760_write(struct device *dev, char *buf, int addr, + size_t count); +extern int w1_ds2760_store_eeprom(struct device *dev, int addr); +extern int w1_ds2760_recall_eeprom(struct device *dev, int addr); + +#endif /* !__w1_ds2760_h__ */ diff --git a/drivers/w1/slaves/w1_ds2780.c b/drivers/w1/slaves/w1_ds2780.c new file mode 100644 index 00000000..505b17d8 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2780.c @@ -0,0 +1,239 @@ +/* + * 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 "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" +#include "w1_ds2780.h" + +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->mutex); + + ret = w1_ds2780_do_io(dev, buf, addr, count, io); + + mutex_unlock(&sl->master->mutex); + + return ret; +} +EXPORT_SYMBOL(w1_ds2780_io); + +int w1_ds2780_io_nolock(struct device *dev, char *buf, int addr, size_t count, + int io) +{ + int ret; + + if (!dev) + return -ENODEV; + + ret = w1_ds2780_do_io(dev, buf, addr, count, io); + + return ret; +} +EXPORT_SYMBOL(w1_ds2780_io_nolock); + +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->mutex); + + if (w1_reset_select_slave(sl) == 0) { + w1_write_8(sl->master, cmd); + w1_write_8(sl->master, addr); + } + + mutex_unlock(&sl->master->mutex); + return 0; +} +EXPORT_SYMBOL(w1_ds2780_eeprom_cmd); + +static ssize_t w1_ds2780_read_bin(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 struct bin_attribute w1_ds2780_bin_attr = { + .attr = { + .name = "w1_slave", + .mode = S_IRUGO, + }, + .size = DS2780_DATA_SIZE, + .read = w1_ds2780_read_bin, +}; + +static DEFINE_IDR(bat_idr); +static DEFINE_MUTEX(bat_idr_lock); + +static int new_bat_id(void) +{ + int ret; + + while (1) { + int id; + + ret = idr_pre_get(&bat_idr, GFP_KERNEL); + if (ret == 0) + return -ENOMEM; + + mutex_lock(&bat_idr_lock); + ret = idr_get_new(&bat_idr, NULL, &id); + mutex_unlock(&bat_idr_lock); + + if (ret == 0) { + ret = id & MAX_ID_MASK; + break; + } else if (ret == -EAGAIN) { + continue; + } else { + break; + } + } + + return ret; +} + +static void release_bat_id(int id) +{ + mutex_lock(&bat_idr_lock); + idr_remove(&bat_idr, id); + mutex_unlock(&bat_idr_lock); +} + +static int w1_ds2780_add_slave(struct w1_slave *sl) +{ + int ret; + int id; + struct platform_device *pdev; + + id = new_bat_id(); + if (id < 0) { + ret = id; + goto noid; + } + + pdev = platform_device_alloc("ds2780-battery", id); + if (!pdev) { + ret = -ENOMEM; + goto pdev_alloc_failed; + } + pdev->dev.parent = &sl->dev; + + ret = platform_device_add(pdev); + if (ret) + goto pdev_add_failed; + + ret = sysfs_create_bin_file(&sl->dev.kobj, &w1_ds2780_bin_attr); + if (ret) + goto bin_attr_failed; + + dev_set_drvdata(&sl->dev, pdev); + + return 0; + +bin_attr_failed: +pdev_add_failed: + platform_device_unregister(pdev); +pdev_alloc_failed: + release_bat_id(id); +noid: + return ret; +} + +static void w1_ds2780_remove_slave(struct w1_slave *sl) +{ + struct platform_device *pdev = dev_get_drvdata(&sl->dev); + int id = pdev->id; + + platform_device_unregister(pdev); + release_bat_id(id); + sysfs_remove_bin_file(&sl->dev.kobj, &w1_ds2780_bin_attr); +} + +static struct w1_family_ops w1_ds2780_fops = { + .add_slave = w1_ds2780_add_slave, + .remove_slave = w1_ds2780_remove_slave, +}; + +static struct w1_family w1_ds2780_family = { + .fid = W1_FAMILY_DS2780, + .fops = &w1_ds2780_fops, +}; + +static int __init w1_ds2780_init(void) +{ + idr_init(&bat_idr); + return w1_register_family(&w1_ds2780_family); +} + +static void __exit w1_ds2780_exit(void) +{ + w1_unregister_family(&w1_ds2780_family); + idr_destroy(&bat_idr); +} + +module_init(w1_ds2780_init); +module_exit(w1_ds2780_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Clifton Barnes <cabarnes@indesign-llc.com>"); +MODULE_DESCRIPTION("1-wire Driver for Maxim/Dallas DS2780 Stand-Alone Fuel Gauge IC"); diff --git a/drivers/w1/slaves/w1_ds2780.h b/drivers/w1/slaves/w1_ds2780.h new file mode 100644 index 00000000..73737936 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2780.h @@ -0,0 +1,131 @@ +/* + * 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_io_nolock(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_smem.c b/drivers/w1/slaves/w1_smem.c new file mode 100644 index 00000000..cc8c02e9 --- /dev/null +++ b/drivers/w1/slaves/w1_smem.c @@ -0,0 +1,70 @@ +/* + * w1_smem.c + * + * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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 "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>"); +MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol, 64bit memory family."); + +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); diff --git a/drivers/w1/slaves/w1_therm.c b/drivers/w1/slaves/w1_therm.c new file mode 100644 index 00000000..17726a05 --- /dev/null +++ b/drivers/w1/slaves/w1_therm.c @@ -0,0 +1,254 @@ +/* + * w1_therm.c + * + * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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/delay.h> + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>"); +MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol, temperature family."); + +/* 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. + */ +static int w1_strong_pullup = 1; +module_param_named(strong_pullup, w1_strong_pullup, int, 0); + +static u8 bad_roms[][9] = { + {0xaa, 0x00, 0x4b, 0x46, 0xff, 0xff, 0x0c, 0x10, 0x87}, + {} + }; + +static ssize_t w1_therm_read(struct device *device, + struct device_attribute *attr, char *buf); + +static struct device_attribute w1_therm_attr = + __ATTR(w1_slave, S_IRUGO, w1_therm_read, NULL); + +static int w1_therm_add_slave(struct w1_slave *sl) +{ + return device_create_file(&sl->dev, &w1_therm_attr); +} + +static void w1_therm_remove_slave(struct w1_slave *sl) +{ + device_remove_file(&sl->dev, &w1_therm_attr); +} + +static struct w1_family_ops w1_therm_fops = { + .add_slave = w1_therm_add_slave, + .remove_slave = w1_therm_remove_slave, +}; + +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, +}; + +struct w1_therm_family_converter +{ + u8 broken; + u16 reserved; + struct w1_family *f; + int (*convert)(u8 rom[9]); +}; + +/* 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 + }, + { + .f = &w1_therm_family_DS1822, + .convert = w1_DS18B20_convert_temp + }, + { + .f = &w1_therm_family_DS18B20, + .convert = w1_DS18B20_convert_temp + }, +}; + +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 int w1_therm_check_rom(u8 rom[9]) +{ + int i; + + for (i=0; i<sizeof(bad_roms)/9; ++i) + if (!memcmp(bad_roms[i], rom, 9)) + return 1; + + return 0; +} + +static ssize_t w1_therm_read(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct w1_master *dev = sl->master; + u8 rom[9], crc, verdict; + int i, max_trying = 10; + ssize_t c = PAGE_SIZE; + + mutex_lock(&dev->mutex); + + memset(rom, 0, sizeof(rom)); + + verdict = 0; + crc = 0; + + while (max_trying--) { + if (!w1_reset_select_slave(sl)) { + int count = 0; + unsigned int tm = 750; + + /* 750ms strong pullup (or delay) after the convert */ + if (w1_strong_pullup) + w1_next_pullup(dev, tm); + w1_write_8(dev, W1_CONVERT_TEMP); + if (!w1_strong_pullup) + msleep(tm); + + if (!w1_reset_select_slave(sl)) { + + w1_write_8(dev, W1_READ_SCRATCHPAD); + if ((count = w1_read_block(dev, rom, 9)) != 9) { + dev_warn(device, "w1_read_block() " + "returned %u instead of 9.\n", + count); + } + + crc = w1_calc_crc8(rom, 8); + + if (rom[8] == crc) + verdict = 1; + } + } + + if (!w1_therm_check_rom(rom)) + break; + } + + for (i = 0; i < 9; ++i) + c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", rom[i]); + c -= snprintf(buf + PAGE_SIZE - c, c, ": crc=%02x %s\n", + crc, (verdict) ? "YES" : "NO"); + if (verdict) + memcpy(sl->rom, rom, sizeof(sl->rom)); + else + dev_warn(device, "18S20 doesn't respond to CONVERT_TEMP.\n"); + + for (i = 0; i < 9; ++i) + c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", sl->rom[i]); + + c -= snprintf(buf + PAGE_SIZE - c, c, "t=%d\n", + w1_convert_temp(rom, sl->family->fid)); + mutex_unlock(&dev->mutex); + + return PAGE_SIZE - c; +} + +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); diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c new file mode 100644 index 00000000..10606c82 --- /dev/null +++ b/drivers/w1/w1.c @@ -0,0 +1,1059 @@ +/* + * w1.c + * + * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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/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 <asm/atomic.h> + +#include "w1.h" +#include "w1_log.h" +#include "w1_int.h" +#include "w1_family.h" +#include "w1_netlink.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>"); +MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol."); + +static int w1_timeout = 10; +int w1_max_slave_count = 10; +int w1_max_slave_ttl = 10; + +module_param_named(timeout, w1_timeout, int, 0); +module_param_named(max_slave_count, w1_max_slave_count, int, 0); +module_param_named(slave_ttl, w1_max_slave_ttl, int, 0); + +DEFINE_MUTEX(w1_mlock); +LIST_HEAD(w1_masters); + +static int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn); + +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.\n", __func__, sl->name); + + while (atomic_read(&sl->refcnt)) { + dev_dbg(dev, "Waiting for %s to become free: refcnt=%d.\n", + sl->name, atomic_read(&sl->refcnt)); + if (msleep_interruptible(1000)) + flush_signals(current); + } + + w1_family_put(sl->family); + sl->master->slave_count--; + + complete(&sl->released); +} + +static ssize_t w1_slave_read_name(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 ssize_t w1_slave_read_id(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 struct device_attribute w1_slave_attr_name = + __ATTR(name, S_IRUGO, w1_slave_read_name, NULL); +static struct device_attribute w1_slave_attr_id = + __ATTR(id, S_IRUGO, w1_slave_read_id, NULL); + +/* Default family */ + +static ssize_t w1_default_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 w1_default_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 struct bin_attribute w1_default_attr = { + .attr = { + .name = "rw", + .mode = S_IRUGO | S_IWUSR, + }, + .size = PAGE_SIZE, + .read = w1_default_read, + .write = w1_default_write, +}; + +static int w1_default_add_slave(struct w1_slave *sl) +{ + return sysfs_create_bin_file(&sl->dev.kobj, &w1_default_attr); +} + +static void w1_default_remove_slave(struct w1_slave *sl) +{ + sysfs_remove_bin_file(&sl->dev.kobj, &w1_default_attr); +} + +static struct w1_family_ops w1_default_fops = { + .add_slave = w1_default_add_slave, + .remove_slave = w1_default_remove_slave, +}; + +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); + + if (strict_strtol(buf, 0, &tmp) == -EINVAL) + return -EINVAL; + + mutex_lock(&md->mutex); + md->search_count = tmp; + mutex_unlock(&md->mutex); + 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); + + if (strict_strtol(buf, 0, &tmp) == -EINVAL) + return -EINVAL; + + mutex_lock(&md->mutex); + md->enable_pullup = tmp; + mutex_unlock(&md->mutex); + wake_up_process(md->thread); + + 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_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; + + mutex_lock(&md->mutex); + + if (md->slave_count == 0) + c -= snprintf(buf + PAGE_SIZE - c, c, "not found.\n"); + else { + struct list_head *ent, *n; + struct w1_slave *sl; + + 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); + } + } + + mutex_unlock(&md->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 hold the mutex + */ +static struct w1_slave *w1_slave_search_device(struct w1_master *dev, + struct w1_reg_num *rn) +{ + struct w1_slave *sl; + 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) { + return sl; + } + } + 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) { + w1_slave_detach(sl); + } 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_RO(max_slave_count, S_IRUGO); +static W1_MASTER_ATTR_RO(attempts, S_IRUGO); +static W1_MASTER_ATTR_RO(timeout, 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_pointer.attr, + &w1_master_attribute_search.attr, + &w1_master_attribute_pullup.attr, + &w1_master_attribute_add.attr, + &w1_master_attribute_remove.attr, + NULL +}; + +static 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); +} + +#ifdef CONFIG_HOTPLUG +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; + + 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) + return 0; + + err = add_uevent_var(env, "W1_FID=%02X", sl->reg_num.family); + if (err) + return err; + + err = add_uevent_var(env, "W1_SLAVE_ID=%024LX", + (unsigned long long)sl->reg_num.id); + if (err) + return err; + + return 0; +}; +#else +static int w1_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + return 0; +} +#endif + +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; + + 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); + + err = device_register(&sl->dev); + if (err < 0) { + dev_err(&sl->dev, + "Device registration [%s] failed. err=%d\n", + dev_name(&sl->dev), err); + return err; + } + + /* Create "name" entry */ + err = device_create_file(&sl->dev, &w1_slave_attr_name); + if (err < 0) { + dev_err(&sl->dev, + "sysfs file creation for [%s] failed. err=%d\n", + dev_name(&sl->dev), err); + goto out_unreg; + } + + /* Create "id" entry */ + err = device_create_file(&sl->dev, &w1_slave_attr_id); + if (err < 0) { + dev_err(&sl->dev, + "sysfs file creation for [%s] failed. err=%d\n", + dev_name(&sl->dev), err); + goto out_rem1; + } + + /* if the family driver needs to initialize something... */ + if (sl->family->fops && sl->family->fops->add_slave && + ((err = sl->family->fops->add_slave(sl)) < 0)) { + dev_err(&sl->dev, + "sysfs file creation for [%s] failed. err=%d\n", + dev_name(&sl->dev), err); + goto out_rem2; + } + + list_add_tail(&sl->w1_slave_entry, &sl->master->slist); + + return 0; + +out_rem2: + device_remove_file(&sl->dev, &w1_slave_attr_id); +out_rem1: + device_remove_file(&sl->dev, &w1_slave_attr_name); +out_unreg: + device_unregister(&sl->dev); + return err; +} + +static 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, (long *)&sl->flags); + + memset(&msg, 0, sizeof(msg)); + memcpy(&sl->reg_num, rn, sizeof(sl->reg_num)); + atomic_set(&sl->refcnt, 0); + init_completion(&sl->released); + + 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); + w1_family_put(sl->family); + kfree(sl); + return err; + } + + sl->ttl = dev->slave_ttl; + dev->slave_count++; + + memcpy(msg.id.id, rn, sizeof(msg.id)); + msg.type = W1_SLAVE_ADD; + w1_netlink_send(dev, &msg); + + return 0; +} + +void w1_slave_detach(struct w1_slave *sl) +{ + struct w1_netlink_msg msg; + + dev_dbg(&sl->dev, "%s: detaching %s [%p].\n", __func__, sl->name, sl); + + list_del(&sl->w1_slave_entry); + + if (sl->family->fops && sl->family->fops->remove_slave) + sl->family->fops->remove_slave(sl); + + 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); + + device_remove_file(&sl->dev, &w1_slave_attr_id); + device_remove_file(&sl->dev, &w1_slave_attr_name); + device_unregister(&sl->dev); + + wait_for_completion(&sl->released); + kfree(sl); +} + +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->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->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); + 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; + + memcpy(&rn, &sl->reg_num, sizeof(rn)); + w1_slave_detach(sl); + + w1_attach_slave_device(dev, &rn); + } + } + dev_dbg(&dev->dev, "Reconnecting slaves in device %s " + "has been finished.\n", dev->name); + 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, (long *)&sl->flags); + } else { + if (rn && tmp->crc == w1_calc_crc8((u8 *)&rn_le, 7)) + w1_attach_slave_device(dev, tmp); + } + + atomic_dec(&dev->refcnt); +} + +/** + * Performs a ROM Search & registers any devices 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 + * + * @dev The master device to search + * @cb Function to call when a device is found + */ +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 = 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. + */ + if (w1_reset_bus(dev)) { + dev_dbg(&dev->dev, "No devices present on the wire.\n"); + 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 (kthread_should_stop()) { + dev_dbg(&dev->dev, "Abort w1_search\n"); + return; + } + } + + if ( (triplet_ret & 0x03) != 0x03 ) { + if ( (desc_bit == last_zero) || (last_zero < 0)) + last_device = 1; + desc_bit = last_zero; + cb(dev, rn); + } + } +} + +void w1_search_process_cb(struct w1_master *dev, u8 search_type, + w1_slave_found_callback cb) +{ + struct w1_slave *sl, *sln; + + list_for_each_entry(sl, &dev->slist, w1_slave_entry) + clear_bit(W1_SLAVE_ACTIVE, (long *)&sl->flags); + + w1_search_devices(dev, search_type, cb); + + list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) { + if (!test_bit(W1_SLAVE_ACTIVE, (unsigned long *)&sl->flags) && !--sl->ttl) + w1_slave_detach(sl); + else if (test_bit(W1_SLAVE_ACTIVE, (unsigned long *)&sl->flags)) + sl->ttl = dev->slave_ttl; + } + + 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); +} + +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 = msecs_to_jiffies(w1_timeout * 1000); + + while (!kthread_should_stop()) { + if (dev->search_count) { + mutex_lock(&dev->mutex); + w1_search_process(dev, W1_SEARCH); + mutex_unlock(&dev->mutex); + } + + try_to_freeze(); + __set_current_state(TASK_INTERRUPTIBLE); + + if (kthread_should_stop()) + break; + + /* Only sleep when the search is active. */ + if (dev->search_count) + schedule_timeout(jtime); + else + schedule(); + } + + atomic_dec(&dev->refcnt); + + return 0; +} + +static int __init w1_init(void) +{ + int retval; + + printk(KERN_INFO "Driver for 1-wire Dallas network protocol.\n"); + + w1_init_netlink(); + + retval = bus_register(&w1_bus_type); + if (retval) { + printk(KERN_ERR "Failed to register bus. err=%d.\n", retval); + goto err_out_exit_init; + } + + retval = driver_register(&w1_master_driver); + if (retval) { + printk(KERN_ERR + "Failed to register master driver. err=%d.\n", + retval); + goto err_out_bus_unregister; + } + + retval = driver_register(&w1_slave_driver); + if (retval) { + printk(KERN_ERR + "Failed to register master 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); diff --git a/drivers/w1/w1.h b/drivers/w1/w1.h new file mode 100644 index 00000000..1ce23fc6 --- /dev/null +++ b/drivers/w1/w1.h @@ -0,0 +1,248 @@ +/* + * w1.h + * + * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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 + */ + +#ifndef __W1_H +#define __W1_H + +struct w1_reg_num +{ +#if defined(__LITTLE_ENDIAN_BITFIELD) + __u64 family:8, + id:48, + crc:8; +#elif defined(__BIG_ENDIAN_BITFIELD) + __u64 crc:8, + id:48, + family:8; +#else +#error "Please fix <asm/byteorder.h>" +#endif +}; + +#ifdef __KERNEL__ + +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/mutex.h> + +#include "w1_family.h" + +#define W1_MAXNAMELEN 32 + +#define W1_SEARCH 0xF0 +#define W1_ALARM_SEARCH 0xEC +#define W1_CONVERT_TEMP 0x44 +#define W1_SKIP_ROM 0xCC +#define W1_READ_SCRATCHPAD 0xBE +#define W1_READ_ROM 0x33 +#define W1_READ_PSUPPLY 0xB4 +#define W1_MATCH_ROM 0x55 +#define W1_RESUME_CMD 0xA5 + +#define W1_SLAVE_ACTIVE 0 + +struct w1_slave +{ + struct module *owner; + unsigned char name[W1_MAXNAMELEN]; + struct list_head w1_slave_entry; + struct w1_reg_num reg_num; + atomic_t refcnt; + u8 rom[9]; + u32 flags; + int ttl; + + struct w1_master *master; + struct w1_family *family; + void *family_data; + struct device dev; + struct completion released; +}; + +typedef void (*w1_slave_found_callback)(struct w1_master *, u64); + + +/** + * Note: read_bit and write_bit are very low level functions and should only + * be used with hardware that doesn't really support 1-wire operations, + * like a parallel/serial port. + * Either define read_bit and write_bit OR define, at minimum, touch_bit and + * reset_bus. + */ +struct w1_bus_master +{ + /** the first parameter in all the functions below */ + void *data; + + /** + * Sample the line level + * @return the level read (0 or 1) + */ + u8 (*read_bit)(void *); + + /** Sets the line level */ + void (*write_bit)(void *, u8); + + /** + * touch_bit is the lowest-level function for devices that really + * support the 1-wire protocol. + * touch_bit(0) = write-0 cycle + * touch_bit(1) = write-1 / read cycle + * @return the bit read (0 or 1) + */ + u8 (*touch_bit)(void *, u8); + + /** + * Reads a bytes. Same as 8 touch_bit(1) calls. + * @return the byte read + */ + u8 (*read_byte)(void *); + + /** + * Writes a byte. Same as 8 touch_bit(x) calls. + */ + void (*write_byte)(void *, u8); + + /** + * Same as a series of read_byte() calls + * @return the number of bytes read + */ + u8 (*read_block)(void *, u8 *, int); + + /** Same as a series of write_byte() calls */ + void (*write_block)(void *, const u8 *, int); + + /** + * Combines two reads and a smart write for ROM searches + * @return bit0=Id bit1=comp_id bit2=dir_taken + */ + u8 (*triplet)(void *, u8); + + /** + * long write-0 with a read for the presence pulse detection + * @return -1=Error, 0=Device present, 1=No device present + */ + u8 (*reset_bus)(void *); + + /** + * Put out a strong pull-up pulse of the specified duration. + * @return -1=Error, 0=completed + */ + u8 (*set_pullup)(void *, int); + + /** Really nice hardware can handles the different types of ROM search + * w1_master* is passed to the slave found callback. + */ + void (*search)(void *, struct w1_master *, + u8, w1_slave_found_callback); +}; + +struct w1_master +{ + struct list_head w1_master_entry; + struct module *owner; + unsigned char name[W1_MAXNAMELEN]; + struct list_head slist; + int max_slave_count, slave_count; + unsigned long attempts; + int slave_ttl; + int initialized; + u32 id; + int search_count; + + atomic_t refcnt; + + void *priv; + int priv_size; + + /** 5V strong pullup enabled flag, 1 enabled, zero disabled. */ + int enable_pullup; + /** 5V strong pullup duration in milliseconds, zero disabled. */ + int pullup_duration; + + struct task_struct *thread; + struct mutex mutex; + + struct device_driver *driver; + struct device dev; + + struct w1_bus_master *bus_master; + + u32 seq; +}; + +int w1_create_master_attributes(struct w1_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); +struct w1_slave *w1_search_slave(struct w1_reg_num *id); +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_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); +void w1_slave_detach(struct w1_slave *sl); + +u8 w1_triplet(struct w1_master *dev, int bdir); +void w1_write_8(struct w1_master *, u8); +u8 w1_read_8(struct w1_master *); +int w1_reset_bus(struct w1_master *); +u8 w1_calc_crc8(u8 *, int); +void w1_write_block(struct w1_master *, const u8 *, int); +void w1_touch_block(struct w1_master *, u8 *, int); +u8 w1_read_block(struct w1_master *, u8 *, int); +int w1_reset_select_slave(struct w1_slave *sl); +int w1_reset_resume_command(struct w1_master *); +void w1_next_pullup(struct w1_master *, int); + +static inline struct w1_slave* dev_to_w1_slave(struct device *dev) +{ + return container_of(dev, struct w1_slave, dev); +} + +static inline struct w1_slave* kobj_to_w1_slave(struct kobject *kobj) +{ + return dev_to_w1_slave(container_of(kobj, struct device, kobj)); +} + +static inline struct w1_master* dev_to_w1_master(struct device *dev) +{ + return container_of(dev, struct w1_master, dev); +} + +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 int w1_process(void *); + +#endif /* __KERNEL__ */ + +#endif /* __W1_H */ diff --git a/drivers/w1/w1_family.c b/drivers/w1/w1_family.c new file mode 100644 index 00000000..4a099041 --- /dev/null +++ b/drivers/w1/w1_family.c @@ -0,0 +1,139 @@ +/* + * w1_family.c + * + * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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/spinlock.h> +#include <linux/list.h> +#include <linux/sched.h> /* schedule_timeout() */ +#include <linux/delay.h> + +#include "w1_family.h" +#include "w1.h" + +DEFINE_SPINLOCK(w1_flock); +static LIST_HEAD(w1_families); + +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; +} + +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)) { + printk(KERN_INFO "Waiting for family %u to become free: refcnt=%d.\n", + fent->fid, atomic_read(&fent->refcnt)); + + if (msleep_interruptible(1000)) + flush_signals(current); + } +} + +/* + * 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_inc(); + atomic_inc(&f->refcnt); + smp_mb__after_atomic_inc(); +} + +EXPORT_SYMBOL(w1_unregister_family); +EXPORT_SYMBOL(w1_register_family); diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h new file mode 100644 index 00000000..97479ae7 --- /dev/null +++ b/drivers/w1/w1_family.h @@ -0,0 +1,70 @@ +/* + * w1_family.h + * + * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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 + */ + +#ifndef __W1_FAMILY_H +#define __W1_FAMILY_H + +#include <linux/types.h> +#include <linux/device.h> +#include <asm/atomic.h> + +#define W1_FAMILY_DEFAULT 0 +#define W1_FAMILY_SMEM_01 0x01 +#define W1_FAMILY_SMEM_81 0x81 +#define W1_THERM_DS18S20 0x10 +#define W1_COUNTER_DS2423 0x1D +#define W1_THERM_DS1822 0x22 +#define W1_EEPROM_DS2433 0x23 +#define W1_THERM_DS18B20 0x28 +#define W1_FAMILY_DS2408 0x29 +#define W1_EEPROM_DS2431 0x2D +#define W1_FAMILY_DS2760 0x30 +#define W1_FAMILY_DS2780 0x32 + +#define MAXNAMELEN 32 + +struct w1_slave; + +struct w1_family_ops +{ + int (* add_slave)(struct w1_slave *); + void (* remove_slave)(struct w1_slave *); +}; + +struct w1_family +{ + struct list_head family_entry; + u8 fid; + + struct w1_family_ops *fops; + + atomic_t refcnt; +}; + +extern spinlock_t w1_flock; + +void w1_family_put(struct w1_family *); +void __w1_family_get(struct w1_family *); +struct w1_family * w1_family_registered(u8); +void w1_unregister_family(struct w1_family *); +int w1_register_family(struct w1_family *); + +#endif /* __W1_FAMILY_H */ diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c new file mode 100644 index 00000000..b50be3f1 --- /dev/null +++ b/drivers/w1/w1_int.c @@ -0,0 +1,250 @@ +/* + * w1_int.c + * + * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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/kernel.h> +#include <linux/list.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/slab.h> + +#include "w1.h" +#include "w1_log.h" +#include "w1_netlink.h" +#include "w1_int.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) { + printk(KERN_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); + mutex_init(&dev->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->driver = driver; + + dev->seq = 1; + + err = device_register(&dev->dev); + if (err) { + printk(KERN_ERR "Failed to register master device. err=%d\n", err); + memset(dev, 0, sizeof(struct w1_master)); + kfree(dev); + dev = NULL; + } + + return dev; +} + +static void w1_free_dev(struct w1_master *dev) +{ + device_unregister(&dev->dev); +} + +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)) { + printk(KERN_ERR "w1_add_master_device: invalid function set\n"); + return(-EINVAL); + } + /* While it would be electrically possible to make a device that + * generated a strong pullup in bit bang mode, only hardare that + * controls 1-wire time frames are even expected to support a strong + * pullup. w1_io.c would need to support calling set_pullup before + * the last write_bit operation of a w1_write_8 which it currently + * doesn't. + */ + if (!master->write_byte && !master->touch_bit && master->set_pullup) { + printk(KERN_ERR "w1_add_master_device: set_pullup requires " + "write_byte or touch_bit, disabling\n"); + master->set_pullup = NULL; + } + + /* 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: + kthread_stop(dev->thread); +#endif +err_out_rm_attr: + w1_destroy_master_attributes(dev); +err_out_free_dev: + w1_free_dev(dev); + + return retval; +} + +void __w1_remove_master_device(struct w1_master *dev) +{ + struct w1_netlink_msg msg; + struct w1_slave *sl, *sln; + + kthread_stop(dev->thread); + + mutex_lock(&w1_mlock); + list_del(&dev->w1_master_entry); + mutex_unlock(&w1_mlock); + + mutex_lock(&dev->mutex); + list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) + w1_slave_detach(sl); + w1_destroy_master_attributes(dev); + 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); + } + + 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); +} + +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) { + printk(KERN_ERR "Device doesn't exist.\n"); + return; + } + + __w1_remove_master_device(found); +} + +EXPORT_SYMBOL(w1_add_master_device); +EXPORT_SYMBOL(w1_remove_master_device); diff --git a/drivers/w1/w1_int.h b/drivers/w1/w1_int.h new file mode 100644 index 00000000..4274082d --- /dev/null +++ b/drivers/w1/w1_int.h @@ -0,0 +1,34 @@ +/* + * w1_int.h + * + * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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 + */ + +#ifndef __W1_INT_H +#define __W1_INT_H + +#include <linux/kernel.h> +#include <linux/device.h> + +#include "w1.h" + +int w1_add_master_device(struct w1_bus_master *); +void w1_remove_master_device(struct w1_bus_master *); +void __w1_remove_master_device(struct w1_master *); + +#endif /* __W1_INT_H */ diff --git a/drivers/w1/w1_io.c b/drivers/w1/w1_io.c new file mode 100644 index 00000000..8e8b64cf --- /dev/null +++ b/drivers/w1/w1_io.c @@ -0,0 +1,433 @@ +/* + * w1_io.c + * + * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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/io.h> + +#include <linux/delay.h> +#include <linux/moduleparam.h> +#include <linux/module.h> + +#include "w1.h" +#include "w1_log.h" + +static int w1_delay_parm = 1; +module_param_named(delay_coef, w1_delay_parm, 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); + +/** + * Generates a write-0 or write-1 cycle and samples the level. + */ +static 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; + } +} + +/** + * Generates a write-0 or write-1 cycle. + * Only call if dev->bus_master->touch_bit is NULL + */ +static void w1_write_bit(struct w1_master *dev, int bit) +{ + 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); + } +} + +/** + * 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. + * + * @param dev the master device + */ +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); + } +} + +/** + * 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. + * + * @param dev the master device + */ +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; + } +} + +/** + * Writes 8 bits. + * + * @param dev the master device + * @param 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); + + +/** + * Generates a write-1 cycle and samples the level. + * Only call if dev->bus_master->touch_bit is NULL + */ +static u8 w1_read_bit(struct w1_master *dev) +{ + int result; + + 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); + w1_delay(55); + + return result & 0x1; +} + +/** + * Does a triplet - used for searching ROM addresses. + * 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. + * + * @param dev the master device + * @param bdir the bit to write if both id_bit and comp_bit are 0 + * @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; + } +} + +/** + * Reads 8 bits. + * + * @param 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); + +/** + * Writes a series of bytes. + * + * @param dev the master device + * @param buf pointer to the data to write + * @param 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); + +/** + * Touches a series of bytes. + * + * @param dev the master device + * @param buf pointer to the data to write + * @param 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); + +/** + * Reads a series of bytes. + * + * @param dev the master device + * @param buf pointer to the buffer to fill + * @param 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); + +/** + * Issues a reset bus sequence. + * + * @param dev The bus master pointer + * @return 0=Device present, 1=No device present or error + */ +int w1_reset_bus(struct w1_master *dev) +{ + int result; + + 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(480); + dev->bus_master->write_bit(dev->bus_master->data, 1); + w1_delay(70); + + result = dev->bus_master->read_bit(dev->bus_master->data) & 0x1; + /* minmum 70 (above) + 410 = 480 us + * There aren't any timing requirements between a reset and + * the following transactions. Sleeping is safe here. + */ + /* w1_delay(410); min required time */ + msleep(1); + } + + 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); +} + +/** + * Resets the bus and then selects the slave by sending either a skip rom + * or a rom match. + * The w1 master lock must be held. + * + * @param sl the slave to select + * @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); + +/** + * 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. + * + * @param dev the master device + */ +int w1_reset_resume_command(struct w1_master *dev) +{ + if (w1_reset_bus(dev)) + return -1; + + /* This will make only the last matched slave perform a skip ROM. */ + w1_write_8(dev, W1_RESUME_CMD); + return 0; +} +EXPORT_SYMBOL_GPL(w1_reset_resume_command); + +/** + * 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. + * + * @param delay time in milliseconds + * @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_log.h b/drivers/w1/w1_log.h new file mode 100644 index 00000000..e6ab7cf0 --- /dev/null +++ b/drivers/w1/w1_log.h @@ -0,0 +1,38 @@ +/* + * w1_log.h + * + * Copyright (c) 2004 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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 + */ + +#ifndef __W1_LOG_H +#define __W1_LOG_H + +#define DEBUG + +#ifdef W1_DEBUG +# define assert(expr) do {} while (0) +#else +# define assert(expr) \ + if(unlikely(!(expr))) { \ + printk(KERN_ERR "Assertion failed! %s,%s,%s,line=%d\n", \ + #expr, __FILE__, __func__, __LINE__); \ + } +#endif + +#endif /* __W1_LOG_H */ + diff --git a/drivers/w1/w1_netlink.c b/drivers/w1/w1_netlink.c new file mode 100644 index 00000000..55aabd92 --- /dev/null +++ b/drivers/w1/w1_netlink.c @@ -0,0 +1,431 @@ +/* + * w1_netlink.c + * + * Copyright (c) 2003 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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/slab.h> +#include <linux/skbuff.h> +#include <linux/netlink.h> +#include <linux/connector.h> + +#include "w1.h" +#include "w1_log.h" +#include "w1_netlink.h" + +#if defined(CONFIG_W1_CON) && (defined(CONFIG_CONNECTOR) || (defined(CONFIG_CONNECTOR_MODULE) && defined(CONFIG_W1_MODULE))) +void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *msg) +{ + char buf[sizeof(struct cn_msg) + sizeof(struct w1_netlink_msg)]; + struct cn_msg *m = (struct cn_msg *)buf; + struct w1_netlink_msg *w = (struct w1_netlink_msg *)(m+1); + + memset(buf, 0, sizeof(buf)); + + m->id.idx = CN_W1_IDX; + m->id.val = CN_W1_VAL; + + m->seq = dev->seq++; + m->len = sizeof(struct w1_netlink_msg); + + memcpy(w, msg, sizeof(struct w1_netlink_msg)); + + cn_netlink_send(m, 0, GFP_KERNEL); +} + +static void w1_send_slave(struct w1_master *dev, u64 rn) +{ + struct cn_msg *msg = dev->priv; + struct w1_netlink_msg *hdr = (struct w1_netlink_msg *)(msg + 1); + struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)(hdr + 1); + int avail; + + /* update kernel slave list */ + w1_slave_found(dev, rn); + + avail = dev->priv_size - cmd->len; + + if (avail > 8) { + u64 *data = (void *)(cmd + 1) + cmd->len; + + *data = rn; + cmd->len += 8; + hdr->len += 8; + msg->len += 8; + return; + } + + msg->ack++; + cn_netlink_send(msg, 0, GFP_KERNEL); + + msg->len = sizeof(struct w1_netlink_msg) + sizeof(struct w1_netlink_cmd); + hdr->len = sizeof(struct w1_netlink_cmd); + cmd->len = 0; +} + +static int w1_process_search_command(struct w1_master *dev, struct cn_msg *msg, + unsigned int avail) +{ + struct w1_netlink_msg *hdr = (struct w1_netlink_msg *)(msg + 1); + struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)(hdr + 1); + int search_type = (cmd->cmd == W1_CMD_ALARM_SEARCH)?W1_ALARM_SEARCH:W1_SEARCH; + + dev->priv = msg; + dev->priv_size = avail; + + w1_search_process_cb(dev, search_type, w1_send_slave); + + msg->ack = 0; + cn_netlink_send(msg, 0, GFP_KERNEL); + + dev->priv = NULL; + dev->priv_size = 0; + + return 0; +} + +static int w1_send_read_reply(struct cn_msg *msg, struct w1_netlink_msg *hdr, + struct w1_netlink_cmd *cmd) +{ + void *data; + struct w1_netlink_msg *h; + struct w1_netlink_cmd *c; + struct cn_msg *cm; + int err; + + data = kzalloc(sizeof(struct cn_msg) + + sizeof(struct w1_netlink_msg) + + sizeof(struct w1_netlink_cmd) + + cmd->len, GFP_KERNEL); + if (!data) + return -ENOMEM; + + cm = (struct cn_msg *)(data); + h = (struct w1_netlink_msg *)(cm + 1); + c = (struct w1_netlink_cmd *)(h + 1); + + memcpy(cm, msg, sizeof(struct cn_msg)); + memcpy(h, hdr, sizeof(struct w1_netlink_msg)); + memcpy(c, cmd, sizeof(struct w1_netlink_cmd)); + + cm->ack = msg->seq+1; + cm->len = sizeof(struct w1_netlink_msg) + + sizeof(struct w1_netlink_cmd) + cmd->len; + + h->len = sizeof(struct w1_netlink_cmd) + cmd->len; + + memcpy(c->data, cmd->data, c->len); + + err = cn_netlink_send(cm, 0, GFP_KERNEL); + + kfree(data); + + return err; +} + +static int w1_process_command_io(struct w1_master *dev, struct cn_msg *msg, + struct w1_netlink_msg *hdr, struct w1_netlink_cmd *cmd) +{ + int err = 0; + + switch (cmd->cmd) { + case W1_CMD_TOUCH: + w1_touch_block(dev, cmd->data, cmd->len); + w1_send_read_reply(msg, hdr, cmd); + break; + case W1_CMD_READ: + w1_read_block(dev, cmd->data, cmd->len); + w1_send_read_reply(msg, hdr, 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_master(struct w1_master *dev, struct cn_msg *req_msg, + struct w1_netlink_msg *req_hdr, struct w1_netlink_cmd *req_cmd) +{ + int err = -EINVAL; + struct cn_msg *msg; + struct w1_netlink_msg *hdr; + struct w1_netlink_cmd *cmd; + + msg = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->id = req_msg->id; + msg->seq = req_msg->seq; + msg->ack = 0; + msg->len = sizeof(struct w1_netlink_msg) + sizeof(struct w1_netlink_cmd); + + hdr = (struct w1_netlink_msg *)(msg + 1); + cmd = (struct w1_netlink_cmd *)(hdr + 1); + + hdr->type = W1_MASTER_CMD; + hdr->id = req_hdr->id; + hdr->len = sizeof(struct w1_netlink_cmd); + + cmd->cmd = req_cmd->cmd; + cmd->len = 0; + + switch (cmd->cmd) { + case W1_CMD_SEARCH: + case W1_CMD_ALARM_SEARCH: + err = w1_process_search_command(dev, msg, + PAGE_SIZE - msg->len - sizeof(struct cn_msg)); + break; + case W1_CMD_READ: + case W1_CMD_WRITE: + case W1_CMD_TOUCH: + err = w1_process_command_io(dev, req_msg, req_hdr, req_cmd); + break; + case W1_CMD_RESET: + err = w1_reset_bus(dev); + break; + default: + err = -EINVAL; + break; + } + + kfree(msg); + return err; +} + +static int w1_process_command_slave(struct w1_slave *sl, struct cn_msg *msg, + struct w1_netlink_msg *hdr, 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, msg, hdr, cmd); +} + +static int w1_process_command_root(struct cn_msg *msg, struct w1_netlink_msg *mcmd) +{ + struct w1_master *m; + struct cn_msg *cn; + struct w1_netlink_msg *w; + u32 *id; + + if (mcmd->type != W1_LIST_MASTERS) { + printk(KERN_NOTICE "%s: msg: %x.%x, wrong type: %u, len: %u.\n", + __func__, msg->id.idx, msg->id.val, mcmd->type, mcmd->len); + return -EPROTO; + } + + cn = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!cn) + return -ENOMEM; + + cn->id.idx = CN_W1_IDX; + cn->id.val = CN_W1_VAL; + + cn->seq = msg->seq; + cn->ack = 1; + cn->len = sizeof(struct w1_netlink_msg); + w = (struct w1_netlink_msg *)(cn + 1); + + w->type = W1_LIST_MASTERS; + w->status = 0; + w->len = 0; + id = (u32 *)(w + 1); + + mutex_lock(&w1_mlock); + list_for_each_entry(m, &w1_masters, w1_master_entry) { + if (cn->len + sizeof(*id) > PAGE_SIZE - sizeof(struct cn_msg)) { + cn_netlink_send(cn, 0, GFP_KERNEL); + cn->ack++; + cn->len = sizeof(struct w1_netlink_msg); + w->len = 0; + id = (u32 *)(w + 1); + } + + *id = m->id; + w->len += sizeof(*id); + cn->len += sizeof(*id); + id++; + } + cn->ack = 0; + cn_netlink_send(cn, 0, GFP_KERNEL); + mutex_unlock(&w1_mlock); + + kfree(cn); + return 0; +} + +static int w1_netlink_send_error(struct cn_msg *rcmsg, struct w1_netlink_msg *rmsg, + struct w1_netlink_cmd *rcmd, int error) +{ + struct cn_msg *cmsg; + struct w1_netlink_msg *msg; + struct w1_netlink_cmd *cmd; + + cmsg = kzalloc(sizeof(*msg) + sizeof(*cmd) + sizeof(*cmsg), GFP_KERNEL); + if (!cmsg) + return -ENOMEM; + + msg = (struct w1_netlink_msg *)(cmsg + 1); + cmd = (struct w1_netlink_cmd *)(msg + 1); + + memcpy(cmsg, rcmsg, sizeof(*cmsg)); + cmsg->len = sizeof(*msg); + + memcpy(msg, rmsg, sizeof(*msg)); + msg->len = 0; + msg->status = (short)-error; + + if (rcmd) { + memcpy(cmd, rcmd, sizeof(*cmd)); + cmd->len = 0; + msg->len += sizeof(*cmd); + cmsg->len += sizeof(*cmd); + } + + error = cn_netlink_send(cmsg, 0, GFP_KERNEL); + kfree(cmsg); + + return error; +} + +static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) +{ + struct w1_netlink_msg *m = (struct w1_netlink_msg *)(msg + 1); + struct w1_netlink_cmd *cmd; + struct w1_slave *sl; + struct w1_master *dev; + int err = 0; + + while (msg->len && !err) { + struct w1_reg_num id; + u16 mlen = m->len; + u8 *cmd_data = m->data; + + dev = NULL; + sl = NULL; + cmd = NULL; + + memcpy(&id, m->id.id, sizeof(id)); +#if 0 + printk("%s: %02x.%012llx.%02x: type=%02x, len=%u.\n", + __func__, id.family, (unsigned long long)id.id, id.crc, m->type, m->len); +#endif + if (m->len + sizeof(struct w1_netlink_msg) > msg->len) { + err = -E2BIG; + break; + } + + if (m->type == W1_MASTER_CMD) { + dev = w1_search_master_id(m->id.mst.id); + } else if (m->type == W1_SLAVE_CMD) { + sl = w1_search_slave(&id); + if (sl) + dev = sl->master; + } else { + err = w1_process_command_root(msg, m); + goto out_cont; + } + + if (!dev) { + err = -ENODEV; + goto out_cont; + } + + err = 0; + if (!mlen) + goto out_cont; + + mutex_lock(&dev->mutex); + + if (sl && w1_reset_select_slave(sl)) { + err = -ENODEV; + goto out_up; + } + + while (mlen) { + cmd = (struct w1_netlink_cmd *)cmd_data; + + if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen) { + err = -E2BIG; + break; + } + + if (sl) + err = w1_process_command_slave(sl, msg, m, cmd); + else + err = w1_process_command_master(dev, msg, m, cmd); + + w1_netlink_send_error(msg, m, cmd, err); + err = 0; + + cmd_data += cmd->len + sizeof(struct w1_netlink_cmd); + mlen -= cmd->len + sizeof(struct w1_netlink_cmd); + } +out_up: + atomic_dec(&dev->refcnt); + if (sl) + atomic_dec(&sl->refcnt); + mutex_unlock(&dev->mutex); +out_cont: + if (!cmd || err) + w1_netlink_send_error(msg, m, cmd, err); + msg->len -= sizeof(struct w1_netlink_msg) + m->len; + m = (struct w1_netlink_msg *)(((u8 *)m) + sizeof(struct w1_netlink_msg) + m->len); + + /* + * Let's allow requests for nonexisting devices. + */ + if (err == -ENODEV) + err = 0; + } +} + +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 *msg) +{ +} + +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 00000000..27e950f9 --- /dev/null +++ b/drivers/w1/w1_netlink.h @@ -0,0 +1,80 @@ +/* + * w1_netlink.h + * + * Copyright (c) 2003 Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * + * + * 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 + */ + +#ifndef __W1_NETLINK_H +#define __W1_NETLINK_H + +#include <asm/types.h> +#include <linux/connector.h> + +#include "w1.h" + +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 +{ + __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 { + W1_CMD_READ = 0, + W1_CMD_WRITE, + W1_CMD_SEARCH, + W1_CMD_ALARM_SEARCH, + W1_CMD_TOUCH, + W1_CMD_RESET, + W1_CMD_MAX, +}; + +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 */ |