mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-07 08:48:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
31
drivers/w1/Kconfig
Normal file
31
drivers/w1/Kconfig
Normal file
|
@ -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
|
9
drivers/w1/Makefile
Normal file
9
drivers/w1/Makefile
Normal file
|
@ -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/
|
||||
|
68
drivers/w1/masters/Kconfig
Normal file
68
drivers/w1/masters/Kconfig
Normal file
|
@ -0,0 +1,68 @@
|
|||
#
|
||||
# 1-wire bus master configuration
|
||||
#
|
||||
|
||||
menu "1-wire Bus Masters"
|
||||
|
||||
config W1_MASTER_MATROX
|
||||
tristate "Matrox G400 transport layer for 1-wire"
|
||||
depends on PCI
|
||||
help
|
||||
Say Y here if you want to communicate with your 1-wire devices
|
||||
using Matrox's G400 GPIO pins.
|
||||
|
||||
This support is also available as a module. If so, the module
|
||||
will be called matrox_w1.
|
||||
|
||||
config W1_MASTER_DS2490
|
||||
tristate "DS2490 USB <-> W1 transport layer for 1-wire"
|
||||
depends on USB
|
||||
help
|
||||
Say Y here if you want to have a driver for DS2490 based USB <-> W1 bridges,
|
||||
for example DS9490*.
|
||||
|
||||
This support is also available as a module. If so, the module
|
||||
will be called ds2490.
|
||||
|
||||
config W1_MASTER_DS2482
|
||||
tristate "Maxim DS2482 I2C to 1-Wire bridge"
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for the Maxim DS2482
|
||||
I2C to 1-Wire bridge.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called ds2482.
|
||||
|
||||
config W1_MASTER_MXC
|
||||
tristate "Freescale MXC 1-wire busmaster"
|
||||
depends on ARCH_MXC || COMPILE_TEST
|
||||
help
|
||||
Say Y here to enable MXC 1-wire host
|
||||
|
||||
config W1_MASTER_DS1WM
|
||||
tristate "Maxim DS1WM 1-wire busmaster"
|
||||
help
|
||||
Say Y here to enable the DS1WM 1-wire driver, such as that
|
||||
in HP iPAQ devices like h5xxx, h2200, and ASIC3-based like
|
||||
hx4700.
|
||||
|
||||
config W1_MASTER_GPIO
|
||||
tristate "GPIO 1-wire busmaster"
|
||||
depends on GPIOLIB
|
||||
help
|
||||
Say Y here if you want to communicate with your 1-wire devices using
|
||||
GPIO pins. This driver uses the GPIO API to control the wire.
|
||||
|
||||
This support is also available as a module. If so, the module
|
||||
will be called w1-gpio.
|
||||
|
||||
config HDQ_MASTER_OMAP
|
||||
tristate "OMAP HDQ driver"
|
||||
depends on ARCH_OMAP
|
||||
help
|
||||
Say Y here if you want support for the 1-wire or HDQ Interface
|
||||
on an OMAP processor.
|
||||
|
||||
endmenu
|
||||
|
12
drivers/w1/masters/Makefile
Normal file
12
drivers/w1/masters/Makefile
Normal file
|
@ -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
|
582
drivers/w1/masters/ds1wm.c
Normal file
582
drivers/w1/masters/ds1wm.c
Normal file
|
@ -0,0 +1,582 @@
|
|||
/*
|
||||
* 1-wire busmaster driver for DS1WM and ASICs with embedded DS1WMs
|
||||
* such as HP iPAQs (including h5xxx, h2200, and devices with ASIC3
|
||||
* like hx4700).
|
||||
*
|
||||
* Copyright (c) 2004-2005, Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>
|
||||
* Copyright (c) 2004-2007, Matt Reimer <mreimer@vpop.net>
|
||||
*
|
||||
* Use consistent with the GNU GPL is permitted,
|
||||
* provided that this copyright notice is
|
||||
* preserved in its entirety in all copies and derived works.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/ds1wm.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
|
||||
#include "../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 device *dev = &ds1wm_data->pdev->dev;
|
||||
struct ds1wm_driver_data *plat = dev_get_platdata(dev);
|
||||
|
||||
if (ds1wm_data->cell->enable)
|
||||
ds1wm_data->cell->enable(ds1wm_data->pdev);
|
||||
|
||||
divisor = ds1wm_find_divisor(plat->clock_rate);
|
||||
dev_dbg(dev, "found divisor 0x%x for clock %d\n",
|
||||
divisor, plat->clock_rate);
|
||||
if (divisor == 0) {
|
||||
dev_err(dev, "no suitable divisor for %dHz clock\n",
|
||||
plat->clock_rate);
|
||||
return;
|
||||
}
|
||||
ds1wm_write_register(ds1wm_data, DS1WM_CLKDIV, divisor);
|
||||
|
||||
/* Let the w1 clock stabilize. */
|
||||
msleep(1);
|
||||
|
||||
ds1wm_reset(ds1wm_data);
|
||||
}
|
||||
|
||||
static void ds1wm_down(struct ds1wm_data *ds1wm_data)
|
||||
{
|
||||
ds1wm_reset(ds1wm_data);
|
||||
|
||||
/* Disable interrupts. */
|
||||
ds1wm_write_register(ds1wm_data, DS1WM_INT_EN,
|
||||
ds1wm_data->int_en_reg_none);
|
||||
|
||||
if (ds1wm_data->cell->disable)
|
||||
ds1wm_data->cell->disable(ds1wm_data->pdev);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
/* w1 methods */
|
||||
|
||||
static u8 ds1wm_read_byte(void *data)
|
||||
{
|
||||
struct ds1wm_data *ds1wm_data = data;
|
||||
|
||||
return ds1wm_read(ds1wm_data, 0xff);
|
||||
}
|
||||
|
||||
static void ds1wm_write_byte(void *data, u8 byte)
|
||||
{
|
||||
struct ds1wm_data *ds1wm_data = data;
|
||||
|
||||
ds1wm_write(ds1wm_data, byte);
|
||||
}
|
||||
|
||||
static u8 ds1wm_reset_bus(void *data)
|
||||
{
|
||||
struct ds1wm_data *ds1wm_data = data;
|
||||
|
||||
ds1wm_reset(ds1wm_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ds1wm_search(void *data, struct w1_master *master_dev,
|
||||
u8 search_type, w1_slave_found_callback slave_found)
|
||||
{
|
||||
struct ds1wm_data *ds1wm_data = data;
|
||||
int i;
|
||||
int ms_discrep_bit = -1;
|
||||
u64 r = 0; /* holds the progress of the search */
|
||||
u64 r_prime, d;
|
||||
unsigned slaves_found = 0;
|
||||
unsigned int pass = 0;
|
||||
|
||||
dev_dbg(&ds1wm_data->pdev->dev, "search begin\n");
|
||||
while (true) {
|
||||
++pass;
|
||||
if (pass > 100) {
|
||||
dev_dbg(&ds1wm_data->pdev->dev,
|
||||
"too many attempts (100), search aborted\n");
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&master_dev->bus_mutex);
|
||||
if (ds1wm_reset(ds1wm_data)) {
|
||||
mutex_unlock(&master_dev->bus_mutex);
|
||||
dev_dbg(&ds1wm_data->pdev->dev,
|
||||
"pass: %d reset error (or no slaves)\n", pass);
|
||||
break;
|
||||
}
|
||||
|
||||
dev_dbg(&ds1wm_data->pdev->dev,
|
||||
"pass: %d r : %0#18llx writing SEARCH_ROM\n", pass, r);
|
||||
ds1wm_write(ds1wm_data, search_type);
|
||||
dev_dbg(&ds1wm_data->pdev->dev,
|
||||
"pass: %d entering ASM\n", pass);
|
||||
ds1wm_write_register(ds1wm_data, DS1WM_CMD, DS1WM_CMD_SRA);
|
||||
dev_dbg(&ds1wm_data->pdev->dev,
|
||||
"pass: %d beginning nibble loop\n", pass);
|
||||
|
||||
r_prime = 0;
|
||||
d = 0;
|
||||
/* we work one nibble at a time */
|
||||
/* each nibble is interleaved to form a byte */
|
||||
for (i = 0; i < 16; i++) {
|
||||
|
||||
unsigned char resp, _r, _r_prime, _d;
|
||||
|
||||
_r = (r >> (4*i)) & 0xf;
|
||||
_r = ((_r & 0x1) << 1) |
|
||||
((_r & 0x2) << 2) |
|
||||
((_r & 0x4) << 3) |
|
||||
((_r & 0x8) << 4);
|
||||
|
||||
/* writes _r, then reads back: */
|
||||
resp = ds1wm_read(ds1wm_data, _r);
|
||||
|
||||
if (ds1wm_data->read_error) {
|
||||
dev_err(&ds1wm_data->pdev->dev,
|
||||
"pass: %d nibble: %d read error\n", pass, i);
|
||||
break;
|
||||
}
|
||||
|
||||
_r_prime = ((resp & 0x02) >> 1) |
|
||||
((resp & 0x08) >> 2) |
|
||||
((resp & 0x20) >> 3) |
|
||||
((resp & 0x80) >> 4);
|
||||
|
||||
_d = ((resp & 0x01) >> 0) |
|
||||
((resp & 0x04) >> 1) |
|
||||
((resp & 0x10) >> 2) |
|
||||
((resp & 0x40) >> 3);
|
||||
|
||||
r_prime |= (unsigned long long) _r_prime << (i * 4);
|
||||
d |= (unsigned long long) _d << (i * 4);
|
||||
|
||||
}
|
||||
if (ds1wm_data->read_error) {
|
||||
mutex_unlock(&master_dev->bus_mutex);
|
||||
dev_err(&ds1wm_data->pdev->dev,
|
||||
"pass: %d read error, retrying\n", pass);
|
||||
break;
|
||||
}
|
||||
dev_dbg(&ds1wm_data->pdev->dev,
|
||||
"pass: %d r\': %0#18llx d:%0#18llx\n",
|
||||
pass, r_prime, d);
|
||||
dev_dbg(&ds1wm_data->pdev->dev,
|
||||
"pass: %d nibble loop complete, exiting ASM\n", pass);
|
||||
ds1wm_write_register(ds1wm_data, DS1WM_CMD, ~DS1WM_CMD_SRA);
|
||||
dev_dbg(&ds1wm_data->pdev->dev,
|
||||
"pass: %d resetting bus\n", pass);
|
||||
ds1wm_reset(ds1wm_data);
|
||||
mutex_unlock(&master_dev->bus_mutex);
|
||||
if ((r_prime & ((u64)1 << 63)) && (d & ((u64)1 << 63))) {
|
||||
dev_err(&ds1wm_data->pdev->dev,
|
||||
"pass: %d bus error, retrying\n", pass);
|
||||
continue; /* start over */
|
||||
}
|
||||
|
||||
|
||||
dev_dbg(&ds1wm_data->pdev->dev,
|
||||
"pass: %d found %0#18llx\n", pass, r_prime);
|
||||
slave_found(master_dev, r_prime);
|
||||
++slaves_found;
|
||||
dev_dbg(&ds1wm_data->pdev->dev,
|
||||
"pass: %d complete, preparing next pass\n", pass);
|
||||
|
||||
/* any discrepency found which we already choose the
|
||||
'1' branch is now is now irrelevant we reveal the
|
||||
next branch with this: */
|
||||
d &= ~r;
|
||||
/* find last bit set, i.e. the most signif. bit set */
|
||||
ms_discrep_bit = fls64(d) - 1;
|
||||
dev_dbg(&ds1wm_data->pdev->dev,
|
||||
"pass: %d new d:%0#18llx MS discrep bit:%d\n",
|
||||
pass, d, ms_discrep_bit);
|
||||
|
||||
/* prev_ms_discrep_bit = ms_discrep_bit;
|
||||
prepare for next ROM search: */
|
||||
if (ms_discrep_bit == -1)
|
||||
break;
|
||||
|
||||
r = (r & ~(~0ull << (ms_discrep_bit))) | 1 << ms_discrep_bit;
|
||||
} /* end while true */
|
||||
dev_dbg(&ds1wm_data->pdev->dev,
|
||||
"pass: %d total: %d search done ms d bit pos: %d\n", pass,
|
||||
slaves_found, ms_discrep_bit);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
static struct w1_bus_master ds1wm_master = {
|
||||
.read_byte = ds1wm_read_byte,
|
||||
.write_byte = ds1wm_write_byte,
|
||||
.reset_bus = ds1wm_reset_bus,
|
||||
.search = ds1wm_search,
|
||||
};
|
||||
|
||||
static int ds1wm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ds1wm_data *ds1wm_data;
|
||||
struct ds1wm_driver_data *plat;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
if (!pdev)
|
||||
return -ENODEV;
|
||||
|
||||
ds1wm_data = devm_kzalloc(&pdev->dev, sizeof(*ds1wm_data), GFP_KERNEL);
|
||||
if (!ds1wm_data)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, ds1wm_data);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -ENXIO;
|
||||
ds1wm_data->map = devm_ioremap(&pdev->dev, res->start,
|
||||
resource_size(res));
|
||||
if (!ds1wm_data->map)
|
||||
return -ENOMEM;
|
||||
|
||||
/* 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)
|
||||
return -ENODEV;
|
||||
plat = dev_get_platdata(&pdev->dev);
|
||||
if (!plat)
|
||||
return -ENODEV;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||
if (!res)
|
||||
return -ENXIO;
|
||||
ds1wm_data->irq = res->start;
|
||||
ds1wm_data->int_en_reg_none = (plat->active_high ? DS1WM_INTEN_IAS : 0);
|
||||
ds1wm_data->reset_recover_delay = plat->reset_recover_delay;
|
||||
|
||||
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 = devm_request_irq(&pdev->dev, ds1wm_data->irq, ds1wm_isr,
|
||||
IRQF_SHARED, "ds1wm", ds1wm_data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ds1wm_up(ds1wm_data);
|
||||
|
||||
ds1wm_master.data = (void *)ds1wm_data;
|
||||
|
||||
ret = w1_add_master_device(&ds1wm_master);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
ds1wm_down(ds1wm_data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int ds1wm_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct ds1wm_data *ds1wm_data = platform_get_drvdata(pdev);
|
||||
|
||||
ds1wm_down(ds1wm_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ds1wm_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct ds1wm_data *ds1wm_data = platform_get_drvdata(pdev);
|
||||
|
||||
ds1wm_up(ds1wm_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define ds1wm_suspend NULL
|
||||
#define ds1wm_resume NULL
|
||||
#endif
|
||||
|
||||
static int ds1wm_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ds1wm_data *ds1wm_data = platform_get_drvdata(pdev);
|
||||
|
||||
w1_remove_master_device(&ds1wm_master);
|
||||
ds1wm_down(ds1wm_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ds1wm_driver = {
|
||||
.driver = {
|
||||
.name = "ds1wm",
|
||||
},
|
||||
.probe = ds1wm_probe,
|
||||
.remove = ds1wm_remove,
|
||||
.suspend = ds1wm_suspend,
|
||||
.resume = ds1wm_resume
|
||||
};
|
||||
|
||||
static int __init ds1wm_init(void)
|
||||
{
|
||||
pr_info("DS1WM w1 busmaster driver - (c) 2004 Szabolcs Gyurko\n");
|
||||
return platform_driver_register(&ds1wm_driver);
|
||||
}
|
||||
|
||||
static void __exit ds1wm_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&ds1wm_driver);
|
||||
}
|
||||
|
||||
module_init(ds1wm_init);
|
||||
module_exit(ds1wm_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>, "
|
||||
"Matt Reimer <mreimer@vpop.net>,"
|
||||
"Jean-Francois Dagenais <dagenaisj@sonatest.com>");
|
||||
MODULE_DESCRIPTION("DS1WM w1 busmaster driver");
|
596
drivers/w1/masters/ds2482.c
Normal file
596
drivers/w1/masters/ds2482.c
Normal file
|
@ -0,0 +1,596 @@
|
|||
/**
|
||||
* 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 <linux/gpio.h>
|
||||
#include <linux/platform_data/ds2482.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 /* 1-wire speed */
|
||||
#define DS2482_REG_CFG_SPU 0x04 /* strong pull-up */
|
||||
#define DS2482_REG_CFG_PPM 0x02 /* presence pulse masking */
|
||||
#define DS2482_REG_CFG_APU 0x01 /* active pull-up */
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
static int ds2482_suspend(struct device *dev);
|
||||
static int ds2482_resume(struct device *dev);
|
||||
|
||||
/**
|
||||
* Driver data (common to all clients)
|
||||
*/
|
||||
static const struct i2c_device_id ds2482_id[] = {
|
||||
{ "ds2482", 0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct dev_pm_ops ds2482_pm_ops = {
|
||||
.suspend = ds2482_suspend,
|
||||
.resume = ds2482_resume,
|
||||
};
|
||||
|
||||
static struct i2c_driver ds2482_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "ds2482",
|
||||
.pm = &ds2482_pm_ops,
|
||||
},
|
||||
.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;
|
||||
int slpz_gpio;
|
||||
|
||||
/* 1-wire interface(s) */
|
||||
int w1_count; /* 1 or 8 */
|
||||
struct ds2482_w1_chan w1_ch[8];
|
||||
|
||||
/* per-device values */
|
||||
u8 channel;
|
||||
u8 read_prt; /* see DS2482_PTR_CODE_xxx */
|
||||
u8 reg_config;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper to calculate values for configuration register
|
||||
* @param conf the raw config value
|
||||
* @return the value w/ complements that can be written to register
|
||||
*/
|
||||
static inline u8 ds2482_calculate_config(u8 conf)
|
||||
{
|
||||
return conf | ((~conf & 0x0f) << 4);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the read pointer.
|
||||
* @param pdev The ds2482 client pointer
|
||||
* @param read_ptr see DS2482_PTR_CODE_xxx above
|
||||
* @return -1 on failure, 0 on success
|
||||
*/
|
||||
static inline int ds2482_select_register(struct ds2482_data *pdev, u8 read_ptr)
|
||||
{
|
||||
if (pdev->read_prt != read_ptr) {
|
||||
if (i2c_smbus_write_byte_data(pdev->client,
|
||||
DS2482_CMD_SET_READ_PTR,
|
||||
read_ptr) < 0)
|
||||
return -1;
|
||||
|
||||
pdev->read_prt = read_ptr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a command without a parameter
|
||||
* @param pdev The ds2482 client pointer
|
||||
* @param cmd DS2482_CMD_RESET,
|
||||
* DS2482_CMD_1WIRE_RESET,
|
||||
* DS2482_CMD_1WIRE_READ_BYTE
|
||||
* @return -1 on failure, 0 on success
|
||||
*/
|
||||
static inline int ds2482_send_cmd(struct ds2482_data *pdev, u8 cmd)
|
||||
{
|
||||
if (i2c_smbus_write_byte(pdev->client, cmd) < 0)
|
||||
return -1;
|
||||
|
||||
pdev->read_prt = DS2482_PTR_CODE_STATUS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a command with a parameter
|
||||
* @param pdev The ds2482 client pointer
|
||||
* @param cmd DS2482_CMD_WRITE_CONFIG,
|
||||
* DS2482_CMD_1WIRE_SINGLE_BIT,
|
||||
* DS2482_CMD_1WIRE_WRITE_BYTE,
|
||||
* DS2482_CMD_1WIRE_TRIPLET
|
||||
* @param byte The data to send
|
||||
* @return -1 on failure, 0 on success
|
||||
*/
|
||||
static inline int ds2482_send_cmd_data(struct ds2482_data *pdev,
|
||||
u8 cmd, u8 byte)
|
||||
{
|
||||
if (i2c_smbus_write_byte_data(pdev->client, cmd, byte) < 0)
|
||||
return -1;
|
||||
|
||||
/* all cmds leave in STATUS, except CONFIG */
|
||||
pdev->read_prt = (cmd != DS2482_CMD_WRITE_CONFIG) ?
|
||||
DS2482_PTR_CODE_STATUS : DS2482_PTR_CODE_CONFIG;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 1-Wire interface code
|
||||
*/
|
||||
|
||||
#define DS2482_WAIT_IDLE_TIMEOUT 100
|
||||
|
||||
/**
|
||||
* Waits until the 1-wire interface is idle (not busy)
|
||||
*
|
||||
* @param pdev Pointer to the device structure
|
||||
* @return the last value read from status or -1 (failure)
|
||||
*/
|
||||
static int ds2482_wait_1wire_idle(struct ds2482_data *pdev)
|
||||
{
|
||||
int temp = -1;
|
||||
int retries = 0;
|
||||
|
||||
if (!ds2482_select_register(pdev, DS2482_PTR_CODE_STATUS)) {
|
||||
do {
|
||||
temp = i2c_smbus_read_byte(pdev->client);
|
||||
} while ((temp >= 0) && (temp & DS2482_REG_STS_1WB) &&
|
||||
(++retries < DS2482_WAIT_IDLE_TIMEOUT));
|
||||
}
|
||||
|
||||
if (retries >= DS2482_WAIT_IDLE_TIMEOUT)
|
||||
pr_err("%s: timeout on channel %d\n",
|
||||
__func__, pdev->channel);
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a w1 channel.
|
||||
* The 1-wire interface must be idle before calling this function.
|
||||
*
|
||||
* @param pdev The ds2482 client pointer
|
||||
* @param channel 0-7
|
||||
* @return -1 (failure) or 0 (success)
|
||||
*/
|
||||
static int ds2482_set_channel(struct ds2482_data *pdev, u8 channel)
|
||||
{
|
||||
if (i2c_smbus_write_byte_data(pdev->client, DS2482_CMD_CHANNEL_SELECT,
|
||||
ds2482_chan_wr[channel]) < 0)
|
||||
return -1;
|
||||
|
||||
pdev->read_prt = DS2482_PTR_CODE_CHANNEL;
|
||||
pdev->channel = -1;
|
||||
if (i2c_smbus_read_byte(pdev->client) == ds2482_chan_rd[channel]) {
|
||||
pdev->channel = channel;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Performs the touch-bit function, which writes a 0 or 1 and reads the level.
|
||||
*
|
||||
* @param data The ds2482 channel pointer
|
||||
* @param bit The level to write: 0 or non-zero
|
||||
* @return The level read: 0 or 1
|
||||
*/
|
||||
static u8 ds2482_w1_touch_bit(void *data, u8 bit)
|
||||
{
|
||||
struct ds2482_w1_chan *pchan = data;
|
||||
struct ds2482_data *pdev = pchan->pdev;
|
||||
int status = -1;
|
||||
|
||||
mutex_lock(&pdev->access_lock);
|
||||
|
||||
/* Select the channel */
|
||||
ds2482_wait_1wire_idle(pdev);
|
||||
if (pdev->w1_count > 1)
|
||||
ds2482_set_channel(pdev, pchan->channel);
|
||||
|
||||
/* Send the touch command, wait until 1WB == 0, return the status */
|
||||
if (!ds2482_send_cmd_data(pdev, DS2482_CMD_1WIRE_SINGLE_BIT,
|
||||
bit ? 0xFF : 0))
|
||||
status = ds2482_wait_1wire_idle(pdev);
|
||||
|
||||
mutex_unlock(&pdev->access_lock);
|
||||
|
||||
return (status & DS2482_REG_STS_SBR) ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the triplet function, which reads two bits and writes a bit.
|
||||
* The bit written is determined by the two reads:
|
||||
* 00 => dbit, 01 => 0, 10 => 1
|
||||
*
|
||||
* @param data The ds2482 channel pointer
|
||||
* @param dbit The direction to choose if both branches are valid
|
||||
* @return b0=read1 b1=read2 b3=bit written
|
||||
*/
|
||||
static u8 ds2482_w1_triplet(void *data, u8 dbit)
|
||||
{
|
||||
struct ds2482_w1_chan *pchan = data;
|
||||
struct ds2482_data *pdev = pchan->pdev;
|
||||
int status = (3 << 5);
|
||||
|
||||
mutex_lock(&pdev->access_lock);
|
||||
|
||||
/* Select the channel */
|
||||
ds2482_wait_1wire_idle(pdev);
|
||||
if (pdev->w1_count > 1)
|
||||
ds2482_set_channel(pdev, pchan->channel);
|
||||
|
||||
/* Send the triplet command, wait until 1WB == 0, return the status */
|
||||
if (!ds2482_send_cmd_data(pdev, DS2482_CMD_1WIRE_TRIPLET,
|
||||
dbit ? 0xFF : 0))
|
||||
status = ds2482_wait_1wire_idle(pdev);
|
||||
|
||||
mutex_unlock(&pdev->access_lock);
|
||||
|
||||
/* Decode the status */
|
||||
return (status >> 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the write byte function.
|
||||
*
|
||||
* @param data The ds2482 channel pointer
|
||||
* @param byte The value to write
|
||||
*/
|
||||
static void ds2482_w1_write_byte(void *data, u8 byte)
|
||||
{
|
||||
struct ds2482_w1_chan *pchan = data;
|
||||
struct ds2482_data *pdev = pchan->pdev;
|
||||
|
||||
mutex_lock(&pdev->access_lock);
|
||||
|
||||
/* Select the channel */
|
||||
ds2482_wait_1wire_idle(pdev);
|
||||
if (pdev->w1_count > 1)
|
||||
ds2482_set_channel(pdev, pchan->channel);
|
||||
|
||||
/* Send the write byte command */
|
||||
ds2482_send_cmd_data(pdev, DS2482_CMD_1WIRE_WRITE_BYTE, byte);
|
||||
|
||||
mutex_unlock(&pdev->access_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the read byte function.
|
||||
*
|
||||
* @param data The ds2482 channel pointer
|
||||
* @return The value read
|
||||
*/
|
||||
static u8 ds2482_w1_read_byte(void *data)
|
||||
{
|
||||
struct ds2482_w1_chan *pchan = data;
|
||||
struct ds2482_data *pdev = pchan->pdev;
|
||||
int result;
|
||||
|
||||
mutex_lock(&pdev->access_lock);
|
||||
|
||||
/* Select the channel */
|
||||
ds2482_wait_1wire_idle(pdev);
|
||||
if (pdev->w1_count > 1)
|
||||
ds2482_set_channel(pdev, pchan->channel);
|
||||
|
||||
/* Send the read byte command */
|
||||
ds2482_send_cmd(pdev, DS2482_CMD_1WIRE_READ_BYTE);
|
||||
|
||||
/* Wait until 1WB == 0 */
|
||||
ds2482_wait_1wire_idle(pdev);
|
||||
|
||||
/* Select the data register */
|
||||
ds2482_select_register(pdev, DS2482_PTR_CODE_DATA);
|
||||
|
||||
/* Read the data byte */
|
||||
result = i2c_smbus_read_byte(pdev->client);
|
||||
|
||||
mutex_unlock(&pdev->access_lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a reset on the 1-wire interface
|
||||
*
|
||||
* @param data The ds2482 channel pointer
|
||||
* @return 0=Device present, 1=No device present or error
|
||||
*/
|
||||
static u8 ds2482_w1_reset_bus(void *data)
|
||||
{
|
||||
struct ds2482_w1_chan *pchan = data;
|
||||
struct ds2482_data *pdev = pchan->pdev;
|
||||
int err;
|
||||
u8 retval = 1;
|
||||
|
||||
mutex_lock(&pdev->access_lock);
|
||||
|
||||
/* Select the channel */
|
||||
ds2482_wait_1wire_idle(pdev);
|
||||
if (pdev->w1_count > 1)
|
||||
ds2482_set_channel(pdev, pchan->channel);
|
||||
|
||||
/* Send the reset command */
|
||||
err = ds2482_send_cmd(pdev, DS2482_CMD_1WIRE_RESET);
|
||||
if (err >= 0) {
|
||||
/* Wait until the reset is complete */
|
||||
err = ds2482_wait_1wire_idle(pdev);
|
||||
retval = !(err & DS2482_REG_STS_PPD);
|
||||
|
||||
/* If the chip did reset since detect, re-config it */
|
||||
if (err & DS2482_REG_STS_RST)
|
||||
ds2482_send_cmd_data(pdev, DS2482_CMD_WRITE_CONFIG,
|
||||
ds2482_calculate_config(0x00));
|
||||
}
|
||||
|
||||
mutex_unlock(&pdev->access_lock);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static u8 ds2482_w1_set_pullup(void *data, int delay)
|
||||
{
|
||||
struct ds2482_w1_chan *pchan = data;
|
||||
struct ds2482_data *pdev = pchan->pdev;
|
||||
u8 retval = 1;
|
||||
|
||||
/* if delay is non-zero activate the pullup,
|
||||
* the strong pullup will be automatically deactivated
|
||||
* by the master, so do not explicitly deactive it
|
||||
*/
|
||||
if (delay) {
|
||||
/* both waits are crucial, otherwise devices might not be
|
||||
* powered long enough, causing e.g. a w1_therm sensor to
|
||||
* provide wrong conversion results
|
||||
*/
|
||||
ds2482_wait_1wire_idle(pdev);
|
||||
/* note: it seems like both SPU and APU have to be set! */
|
||||
retval = ds2482_send_cmd_data(pdev, DS2482_CMD_WRITE_CONFIG,
|
||||
ds2482_calculate_config(DS2482_REG_CFG_SPU |
|
||||
DS2482_REG_CFG_APU));
|
||||
ds2482_wait_1wire_idle(pdev);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int ds2482_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct ds2482_data *data = i2c_get_clientdata(client);
|
||||
|
||||
if (data->slpz_gpio >= 0)
|
||||
gpio_set_value(data->slpz_gpio, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ds2482_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct ds2482_data *data = i2c_get_clientdata(client);
|
||||
|
||||
if (data->slpz_gpio >= 0)
|
||||
gpio_set_value(data->slpz_gpio, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ds2482_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct ds2482_data *data;
|
||||
struct ds2482_platform_data *pdata;
|
||||
int err = -ENODEV;
|
||||
int temp1;
|
||||
int idx;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_BYTE))
|
||||
return -ENODEV;
|
||||
|
||||
if (!(data = kzalloc(sizeof(struct ds2482_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
data->client = client;
|
||||
i2c_set_clientdata(client, data);
|
||||
|
||||
/* Reset the device (sets the read_ptr to status) */
|
||||
if (ds2482_send_cmd(data, DS2482_CMD_RESET) < 0) {
|
||||
dev_warn(&client->dev, "DS2482 reset failed.\n");
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
/* Sleep at least 525ns to allow the reset to complete */
|
||||
ndelay(525);
|
||||
|
||||
/* Read the status byte - only reset bit and line should be set */
|
||||
temp1 = i2c_smbus_read_byte(client);
|
||||
if (temp1 != (DS2482_REG_STS_LL | DS2482_REG_STS_RST)) {
|
||||
dev_warn(&client->dev, "DS2482 reset status "
|
||||
"0x%02X - not a DS2482\n", temp1);
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
/* Detect the 8-port version */
|
||||
data->w1_count = 1;
|
||||
if (ds2482_set_channel(data, 7) == 0)
|
||||
data->w1_count = 8;
|
||||
|
||||
/* Set all config items to 0 (off) */
|
||||
ds2482_send_cmd_data(data, DS2482_CMD_WRITE_CONFIG,
|
||||
ds2482_calculate_config(0x00));
|
||||
|
||||
mutex_init(&data->access_lock);
|
||||
|
||||
/* Register 1-wire interface(s) */
|
||||
for (idx = 0; idx < data->w1_count; idx++) {
|
||||
data->w1_ch[idx].pdev = data;
|
||||
data->w1_ch[idx].channel = idx;
|
||||
|
||||
/* Populate all the w1 bus master stuff */
|
||||
data->w1_ch[idx].w1_bm.data = &data->w1_ch[idx];
|
||||
data->w1_ch[idx].w1_bm.read_byte = ds2482_w1_read_byte;
|
||||
data->w1_ch[idx].w1_bm.write_byte = ds2482_w1_write_byte;
|
||||
data->w1_ch[idx].w1_bm.touch_bit = ds2482_w1_touch_bit;
|
||||
data->w1_ch[idx].w1_bm.triplet = ds2482_w1_triplet;
|
||||
data->w1_ch[idx].w1_bm.reset_bus = ds2482_w1_reset_bus;
|
||||
data->w1_ch[idx].w1_bm.set_pullup = ds2482_w1_set_pullup;
|
||||
|
||||
err = w1_add_master_device(&data->w1_ch[idx].w1_bm);
|
||||
if (err) {
|
||||
data->w1_ch[idx].pdev = NULL;
|
||||
goto exit_w1_remove;
|
||||
}
|
||||
}
|
||||
|
||||
pdata = client->dev.platform_data;
|
||||
data->slpz_gpio = pdata ? pdata->slpz_gpio : -1;
|
||||
|
||||
if (data->slpz_gpio >= 0) {
|
||||
err = gpio_request_one(data->slpz_gpio, GPIOF_OUT_INIT_HIGH,
|
||||
"ds2482.slpz");
|
||||
if (err < 0)
|
||||
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);
|
||||
}
|
||||
|
||||
if (data->slpz_gpio >= 0) {
|
||||
gpio_set_value(data->slpz_gpio, 0);
|
||||
gpio_free(data->slpz_gpio);
|
||||
}
|
||||
|
||||
/* Free the memory */
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_i2c_driver(ds2482_driver);
|
||||
|
||||
MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>");
|
||||
MODULE_DESCRIPTION("DS2482 driver");
|
||||
MODULE_LICENSE("GPL");
|
1090
drivers/w1/masters/ds2490.c
Normal file
1090
drivers/w1/masters/ds2490.c
Normal file
File diff suppressed because it is too large
Load diff
247
drivers/w1/masters/matrox_w1.c
Normal file
247
drivers/w1/masters/matrox_w1.c
Normal file
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* matrox_w1.c
|
||||
*
|
||||
* Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <asm/types.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pci_ids.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include "../w1.h"
|
||||
#include "../w1_int.h"
|
||||
#include "../w1_log.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>");
|
||||
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 matrox_w1_probe(struct pci_dev *, const struct pci_device_id *);
|
||||
static void 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 = 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 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 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);
|
188
drivers/w1/masters/mxc_w1.c
Normal file
188
drivers/w1/masters/mxc_w1.c
Normal file
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Copyright 2005-2008 Freescale Semiconductor, Inc. All Rights Reserved.
|
||||
* Copyright 2008 Luotao Fu, kernel@pengutronix.de
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "../w1.h"
|
||||
#include "../w1_int.h"
|
||||
|
||||
/*
|
||||
* MXC W1 Register offsets
|
||||
*/
|
||||
#define MXC_W1_CONTROL 0x00
|
||||
# define MXC_W1_CONTROL_RDST BIT(3)
|
||||
# define MXC_W1_CONTROL_WR(x) BIT(5 - (x))
|
||||
# define MXC_W1_CONTROL_PST BIT(6)
|
||||
# define MXC_W1_CONTROL_RPP BIT(7)
|
||||
#define MXC_W1_TIME_DIVIDER 0x02
|
||||
#define MXC_W1_RESET 0x04
|
||||
# define MXC_W1_RESET_RST BIT(0)
|
||||
|
||||
struct mxc_w1_device {
|
||||
void __iomem *regs;
|
||||
struct clk *clk;
|
||||
struct w1_bus_master bus_master;
|
||||
};
|
||||
|
||||
/*
|
||||
* this is the low level routine to
|
||||
* reset the device on the One Wire interface
|
||||
* on the hardware
|
||||
*/
|
||||
static u8 mxc_w1_ds2_reset_bus(void *data)
|
||||
{
|
||||
struct mxc_w1_device *dev = data;
|
||||
unsigned long timeout;
|
||||
|
||||
writeb(MXC_W1_CONTROL_RPP, dev->regs + MXC_W1_CONTROL);
|
||||
|
||||
/* Wait for reset sequence 511+512us, use 1500us for sure */
|
||||
timeout = jiffies + usecs_to_jiffies(1500);
|
||||
|
||||
udelay(511 + 512);
|
||||
|
||||
do {
|
||||
u8 ctrl = readb(dev->regs + MXC_W1_CONTROL);
|
||||
|
||||
/* PST bit is valid after the RPP bit is self-cleared */
|
||||
if (!(ctrl & MXC_W1_CONTROL_RPP))
|
||||
return !(ctrl & MXC_W1_CONTROL_PST);
|
||||
} while (time_is_after_jiffies(timeout));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* this is the low level routine to read/write a bit on the One Wire
|
||||
* interface on the hardware. It does write 0 if parameter bit is set
|
||||
* to 0, otherwise a write 1/read.
|
||||
*/
|
||||
static u8 mxc_w1_ds2_touch_bit(void *data, u8 bit)
|
||||
{
|
||||
struct mxc_w1_device *dev = data;
|
||||
unsigned long timeout;
|
||||
|
||||
writeb(MXC_W1_CONTROL_WR(bit), dev->regs + MXC_W1_CONTROL);
|
||||
|
||||
/* Wait for read/write bit (60us, Max 120us), use 200us for sure */
|
||||
timeout = jiffies + usecs_to_jiffies(200);
|
||||
|
||||
udelay(60);
|
||||
|
||||
do {
|
||||
u8 ctrl = readb(dev->regs + MXC_W1_CONTROL);
|
||||
|
||||
/* RDST bit is valid after the WR1/RD bit is self-cleared */
|
||||
if (!(ctrl & MXC_W1_CONTROL_WR(bit)))
|
||||
return !!(ctrl & MXC_W1_CONTROL_RDST);
|
||||
} while (time_is_after_jiffies(timeout));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_w1_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mxc_w1_device *mdev;
|
||||
unsigned long clkrate;
|
||||
struct resource *res;
|
||||
unsigned int clkdiv;
|
||||
int err;
|
||||
|
||||
mdev = devm_kzalloc(&pdev->dev, sizeof(struct mxc_w1_device),
|
||||
GFP_KERNEL);
|
||||
if (!mdev)
|
||||
return -ENOMEM;
|
||||
|
||||
mdev->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(mdev->clk))
|
||||
return PTR_ERR(mdev->clk);
|
||||
|
||||
clkrate = clk_get_rate(mdev->clk);
|
||||
if (clkrate < 10000000)
|
||||
dev_warn(&pdev->dev,
|
||||
"Low clock frequency causes improper function\n");
|
||||
|
||||
clkdiv = DIV_ROUND_CLOSEST(clkrate, 1000000);
|
||||
clkrate /= clkdiv;
|
||||
if ((clkrate < 980000) || (clkrate > 1020000))
|
||||
dev_warn(&pdev->dev,
|
||||
"Incorrect time base frequency %lu Hz\n", clkrate);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
mdev->regs = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(mdev->regs))
|
||||
return PTR_ERR(mdev->regs);
|
||||
|
||||
err = clk_prepare_enable(mdev->clk);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Software reset 1-Wire module */
|
||||
writeb(MXC_W1_RESET_RST, mdev->regs + MXC_W1_RESET);
|
||||
writeb(0, mdev->regs + MXC_W1_RESET);
|
||||
|
||||
writeb(clkdiv - 1, mdev->regs + MXC_W1_TIME_DIVIDER);
|
||||
|
||||
mdev->bus_master.data = mdev;
|
||||
mdev->bus_master.reset_bus = mxc_w1_ds2_reset_bus;
|
||||
mdev->bus_master.touch_bit = mxc_w1_ds2_touch_bit;
|
||||
|
||||
platform_set_drvdata(pdev, mdev);
|
||||
|
||||
err = w1_add_master_device(&mdev->bus_master);
|
||||
if (err)
|
||||
clk_disable_unprepare(mdev->clk);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* disassociate the w1 device from the driver
|
||||
*/
|
||||
static int mxc_w1_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mxc_w1_device *mdev = platform_get_drvdata(pdev);
|
||||
|
||||
w1_remove_master_device(&mdev->bus_master);
|
||||
|
||||
clk_disable_unprepare(mdev->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id mxc_w1_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx21-owire" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mxc_w1_dt_ids);
|
||||
|
||||
static struct platform_driver mxc_w1_driver = {
|
||||
.driver = {
|
||||
.name = "mxc_w1",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = mxc_w1_dt_ids,
|
||||
},
|
||||
.probe = mxc_w1_probe,
|
||||
.remove = mxc_w1_remove,
|
||||
};
|
||||
module_platform_driver(mxc_w1_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Freescale Semiconductors Inc");
|
||||
MODULE_DESCRIPTION("Driver for One-Wire on MXC");
|
635
drivers/w1/masters/omap_hdq.c
Normal file
635
drivers/w1/masters/omap_hdq.c
Normal file
|
@ -0,0 +1,635 @@
|
|||
/*
|
||||
* drivers/w1/masters/omap_hdq.c
|
||||
*
|
||||
* Copyright (C) 2007,2012 Texas Instruments, Inc.
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "../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;
|
||||
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 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_readl(hdq_data->hdq_base + offset);
|
||||
}
|
||||
|
||||
static inline void hdq_reg_out(struct hdq_data *hdq_data, u32 offset, u8 val)
|
||||
{
|
||||
__raw_writel(val, hdq_data->hdq_base + offset);
|
||||
}
|
||||
|
||||
static inline u8 hdq_reg_merge(struct hdq_data *hdq_data, u32 offset,
|
||||
u8 val, u8 mask)
|
||||
{
|
||||
u8 new_val = (__raw_readl(hdq_data->hdq_base + offset) & ~mask)
|
||||
| (val & mask);
|
||||
__raw_writel(new_val, hdq_data->hdq_base + offset);
|
||||
|
||||
return new_val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait for one or more bits in flag change.
|
||||
* HDQ_FLAG_SET: wait until any bit in the flag is set.
|
||||
* HDQ_FLAG_CLEAR: wait until all bits in the flag are cleared.
|
||||
* return 0 on success and -ETIMEDOUT in the case of timeout.
|
||||
*/
|
||||
static int hdq_wait_for_flag(struct hdq_data *hdq_data, u32 offset,
|
||||
u8 flag, u8 flag_set, u8 *status)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
|
||||
|
||||
if (flag_set == OMAP_HDQ_FLAG_CLEAR) {
|
||||
/* wait for the flag clear */
|
||||
while (((*status = hdq_reg_in(hdq_data, offset)) & flag)
|
||||
&& time_before(jiffies, timeout)) {
|
||||
schedule_timeout_uninterruptible(1);
|
||||
}
|
||||
if (*status & flag)
|
||||
ret = -ETIMEDOUT;
|
||||
} else if (flag_set == OMAP_HDQ_FLAG_SET) {
|
||||
/* wait for the flag set */
|
||||
while (!((*status = hdq_reg_in(hdq_data, offset)) & flag)
|
||||
&& time_before(jiffies, timeout)) {
|
||||
schedule_timeout_uninterruptible(1);
|
||||
}
|
||||
if (!(*status & flag))
|
||||
ret = -ETIMEDOUT;
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* write out a byte and fill *status with HDQ_INT_STATUS */
|
||||
static int hdq_write_byte(struct hdq_data *hdq_data, u8 val, u8 *status)
|
||||
{
|
||||
int ret;
|
||||
u8 tmp_status;
|
||||
unsigned long irqflags;
|
||||
|
||||
*status = 0;
|
||||
|
||||
spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
|
||||
/* clear interrupt flags via a dummy read */
|
||||
hdq_reg_in(hdq_data, OMAP_HDQ_INT_STATUS);
|
||||
/* ISR loads it with new INT_STATUS */
|
||||
hdq_data->hdq_irqstatus = 0;
|
||||
spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
|
||||
|
||||
hdq_reg_out(hdq_data, OMAP_HDQ_TX_DATA, val);
|
||||
|
||||
/* set the GO bit */
|
||||
hdq_reg_merge(hdq_data, OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
|
||||
OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
|
||||
/* wait for the TXCOMPLETE bit */
|
||||
ret = wait_event_timeout(hdq_wait_queue,
|
||||
hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
|
||||
if (ret == 0) {
|
||||
dev_dbg(hdq_data->dev, "TX wait elapsed\n");
|
||||
ret = -ETIMEDOUT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*status = hdq_data->hdq_irqstatus;
|
||||
/* check irqstatus */
|
||||
if (!(*status & OMAP_HDQ_INT_STATUS_TXCOMPLETE)) {
|
||||
dev_dbg(hdq_data->dev, "timeout waiting for"
|
||||
" TXCOMPLETE/RXCOMPLETE, %x", *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;
|
||||
|
||||
ret = mutex_lock_interruptible(&hdq_data->hdq_mutex);
|
||||
if (ret < 0) {
|
||||
ret = -EINTR;
|
||||
goto rtn;
|
||||
}
|
||||
|
||||
if (!hdq_data->hdq_usecount) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(hdq_data->hdq_irqstatus & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
|
||||
hdq_reg_merge(hdq_data, OMAP_HDQ_CTRL_STATUS,
|
||||
OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO,
|
||||
OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
|
||||
/*
|
||||
* The RX comes immediately after TX.
|
||||
*/
|
||||
wait_event_timeout(hdq_wait_queue,
|
||||
(hdq_data->hdq_irqstatus
|
||||
& OMAP_HDQ_INT_STATUS_RXCOMPLETE),
|
||||
OMAP_HDQ_TIMEOUT);
|
||||
|
||||
hdq_reg_merge(hdq_data, OMAP_HDQ_CTRL_STATUS, 0,
|
||||
OMAP_HDQ_CTRL_STATUS_DIR);
|
||||
status = hdq_data->hdq_irqstatus;
|
||||
/* check irqstatus */
|
||||
if (!(status & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
|
||||
dev_dbg(hdq_data->dev, "timeout waiting for"
|
||||
" RXCOMPLETE, %x", status);
|
||||
ret = -ETIMEDOUT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
/* the data is ready. Read it in! */
|
||||
*val = hdq_reg_in(hdq_data, OMAP_HDQ_RX_DATA);
|
||||
out:
|
||||
mutex_unlock(&hdq_data->hdq_mutex);
|
||||
rtn:
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
/* Enable clocks and set the controller to HDQ mode */
|
||||
static int omap_hdq_get(struct hdq_data *hdq_data)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = mutex_lock_interruptible(&hdq_data->hdq_mutex);
|
||||
if (ret < 0) {
|
||||
ret = -EINTR;
|
||||
goto rtn;
|
||||
}
|
||||
|
||||
if (OMAP_HDQ_MAX_USER == hdq_data->hdq_usecount) {
|
||||
dev_dbg(hdq_data->dev, "attempt to exceed the max use count");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
} else {
|
||||
hdq_data->hdq_usecount++;
|
||||
try_module_get(THIS_MODULE);
|
||||
if (1 == hdq_data->hdq_usecount) {
|
||||
|
||||
pm_runtime_get_sync(hdq_data->dev);
|
||||
|
||||
/* make sure HDQ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
pm_runtime_put_sync(hdq_data->dev);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static int omap_hdq_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct hdq_data *hdq_data;
|
||||
struct resource *res;
|
||||
int ret, irq;
|
||||
u8 rev;
|
||||
|
||||
hdq_data = devm_kzalloc(dev, sizeof(*hdq_data), GFP_KERNEL);
|
||||
if (!hdq_data) {
|
||||
dev_dbg(&pdev->dev, "unable to allocate memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
hdq_data->dev = dev;
|
||||
platform_set_drvdata(pdev, hdq_data);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
hdq_data->hdq_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(hdq_data->hdq_base))
|
||||
return PTR_ERR(hdq_data->hdq_base);
|
||||
|
||||
hdq_data->hdq_usecount = 0;
|
||||
mutex_init(&hdq_data->hdq_mutex);
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
|
||||
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 = devm_request_irq(dev, irq, hdq_isr, 0, "omap_hdq", hdq_data);
|
||||
if (ret < 0) {
|
||||
dev_dbg(&pdev->dev, "could not request irq\n");
|
||||
goto err_irq;
|
||||
}
|
||||
|
||||
omap_hdq_break(hdq_data);
|
||||
|
||||
pm_runtime_put_sync(&pdev->dev);
|
||||
|
||||
omap_w1_master.data = hdq_data;
|
||||
|
||||
ret = w1_add_master_device(&omap_w1_master);
|
||||
if (ret) {
|
||||
dev_dbg(&pdev->dev, "Failure in registering w1 master\n");
|
||||
goto err_w1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_irq:
|
||||
pm_runtime_put_sync(&pdev->dev);
|
||||
err_w1:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int omap_hdq_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct hdq_data *hdq_data = platform_get_drvdata(pdev);
|
||||
|
||||
mutex_lock(&hdq_data->hdq_mutex);
|
||||
|
||||
if (hdq_data->hdq_usecount) {
|
||||
dev_dbg(&pdev->dev, "removed when use count is not zero\n");
|
||||
mutex_unlock(&hdq_data->hdq_mutex);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
mutex_unlock(&hdq_data->hdq_mutex);
|
||||
|
||||
/* remove module dependency */
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_platform_driver(omap_hdq_driver);
|
||||
|
||||
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");
|
244
drivers/w1/masters/w1-gpio.c
Normal file
244
drivers/w1/masters/w1-gpio.c
Normal file
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* w1-gpio - GPIO w1 bus master driver
|
||||
*
|
||||
* Copyright (C) 2007 Ville Syrjala <syrjala@sci.fi>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2
|
||||
* as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/w1-gpio.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "../w1.h"
|
||||
#include "../w1_int.h"
|
||||
|
||||
static u8 w1_gpio_set_pullup(void *data, int delay)
|
||||
{
|
||||
struct w1_gpio_platform_data *pdata = data;
|
||||
|
||||
if (delay) {
|
||||
pdata->pullup_duration = delay;
|
||||
} else {
|
||||
if (pdata->pullup_duration) {
|
||||
gpio_direction_output(pdata->pin, 1);
|
||||
|
||||
msleep(pdata->pullup_duration);
|
||||
|
||||
gpio_direction_input(pdata->pin);
|
||||
}
|
||||
pdata->pullup_duration = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_OF)
|
||||
static struct of_device_id w1_gpio_dt_ids[] = {
|
||||
{ .compatible = "w1-gpio" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, w1_gpio_dt_ids);
|
||||
#endif
|
||||
|
||||
static int w1_gpio_probe_dt(struct platform_device *pdev)
|
||||
{
|
||||
struct w1_gpio_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
int gpio;
|
||||
|
||||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
|
||||
if (of_get_property(np, "linux,open-drain", NULL))
|
||||
pdata->is_open_drain = 1;
|
||||
|
||||
gpio = of_get_gpio(np, 0);
|
||||
if (gpio < 0) {
|
||||
if (gpio != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"Failed to parse gpio property for data pin (%d)\n",
|
||||
gpio);
|
||||
|
||||
return gpio;
|
||||
}
|
||||
pdata->pin = gpio;
|
||||
|
||||
gpio = of_get_gpio(np, 1);
|
||||
if (gpio == -EPROBE_DEFER)
|
||||
return gpio;
|
||||
/* ignore other errors as the pullup gpio is optional */
|
||||
pdata->ext_pullup_enable_pin = gpio;
|
||||
|
||||
pdev->dev.platform_data = pdata;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int w1_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct w1_bus_master *master;
|
||||
struct w1_gpio_platform_data *pdata;
|
||||
int err;
|
||||
|
||||
if (of_have_populated_dt()) {
|
||||
err = w1_gpio_probe_dt(pdev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
pdata = dev_get_platdata(&pdev->dev);
|
||||
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "No configuration data\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
master = devm_kzalloc(&pdev->dev, sizeof(struct w1_bus_master),
|
||||
GFP_KERNEL);
|
||||
if (!master) {
|
||||
dev_err(&pdev->dev, "Out of memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = devm_gpio_request(&pdev->dev, pdata->pin, "w1");
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "gpio_request (pin) failed\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (gpio_is_valid(pdata->ext_pullup_enable_pin)) {
|
||||
err = devm_gpio_request_one(&pdev->dev,
|
||||
pdata->ext_pullup_enable_pin, GPIOF_INIT_LOW,
|
||||
"w1 pullup");
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "gpio_request_one "
|
||||
"(ext_pullup_enable_pin) failed\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
master->set_pullup = w1_gpio_set_pullup;
|
||||
}
|
||||
|
||||
err = w1_add_master_device(master);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "w1_add_master device failed\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (pdata->enable_external_pullup)
|
||||
pdata->enable_external_pullup(1);
|
||||
|
||||
if (gpio_is_valid(pdata->ext_pullup_enable_pin))
|
||||
gpio_set_value(pdata->ext_pullup_enable_pin, 1);
|
||||
|
||||
platform_set_drvdata(pdev, master);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int w1_gpio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct w1_bus_master *master = platform_get_drvdata(pdev);
|
||||
struct w1_gpio_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
|
||||
if (pdata->enable_external_pullup)
|
||||
pdata->enable_external_pullup(0);
|
||||
|
||||
if (gpio_is_valid(pdata->ext_pullup_enable_pin))
|
||||
gpio_set_value(pdata->ext_pullup_enable_pin, 0);
|
||||
|
||||
w1_remove_master_device(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 = dev_get_platdata(&pdev->dev);
|
||||
|
||||
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 = dev_get_platdata(&pdev->dev);
|
||||
|
||||
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,
|
||||
.of_match_table = of_match_ptr(w1_gpio_dt_ids),
|
||||
},
|
||||
.probe = w1_gpio_probe,
|
||||
.remove = w1_gpio_remove,
|
||||
.suspend = w1_gpio_suspend,
|
||||
.resume = w1_gpio_resume,
|
||||
};
|
||||
|
||||
module_platform_driver(w1_gpio_driver);
|
||||
|
||||
MODULE_DESCRIPTION("GPIO w1 bus master driver");
|
||||
MODULE_AUTHOR("Ville Syrjala <syrjala@sci.fi>");
|
||||
MODULE_LICENSE("GPL");
|
135
drivers/w1/slaves/Kconfig
Normal file
135
drivers/w1/slaves/Kconfig
Normal file
|
@ -0,0 +1,135 @@
|
|||
#
|
||||
# 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_DS2408_READBACK
|
||||
bool "Read-back values written to DS2408's output register"
|
||||
depends on W1_SLAVE_DS2408
|
||||
default y
|
||||
help
|
||||
Enabling this will cause the driver to read back the values written
|
||||
to the chip's output register in order to detect errors.
|
||||
|
||||
This is slower but useful when debugging chips and/or busses.
|
||||
|
||||
config W1_SLAVE_DS2413
|
||||
tristate "Dual Channel Addressable Switch 0x3a family support (DS2413)"
|
||||
help
|
||||
Say Y here if you want to use a 1-wire
|
||||
DS2413 Dual Channel Addressable Switch device support
|
||||
|
||||
config W1_SLAVE_DS2406
|
||||
tristate "Dual Channel Addressable Switch 0x12 family support (DS2406)"
|
||||
select CRC16
|
||||
help
|
||||
Say Y or M here if you want to use a 1-wire
|
||||
DS2406 Dual Channel Addressable Switch. EPROM read/write
|
||||
support for these devices is not implemented.
|
||||
|
||||
config W1_SLAVE_DS2423
|
||||
tristate "Counter 1-wire device (DS2423)"
|
||||
select CRC16
|
||||
help
|
||||
If you enable this you can read the counter values available
|
||||
in the DS2423 chipset from the w1_slave file under the
|
||||
sys file system.
|
||||
|
||||
Say Y here if you want to use a 1-wire
|
||||
counter family device (DS2423).
|
||||
|
||||
config W1_SLAVE_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)"
|
||||
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"
|
||||
help
|
||||
If you enable this you will have the DS2780 battery monitor
|
||||
chip support.
|
||||
|
||||
The battery monitor chip is used in many batteries/devices
|
||||
as the one who is responsible for charging/discharging/monitoring
|
||||
Li+ batteries.
|
||||
|
||||
If you are unsure, say N.
|
||||
|
||||
config W1_SLAVE_DS2781
|
||||
tristate "Dallas 2781 battery monitor chip"
|
||||
help
|
||||
If you enable this you will have the DS2781 battery monitor
|
||||
chip support.
|
||||
|
||||
The battery monitor chip is used in many batteries/devices
|
||||
as the one who is responsible for charging/discharging/monitoring
|
||||
Li+ batteries.
|
||||
|
||||
If you are unsure, say N.
|
||||
|
||||
config W1_SLAVE_DS28E04
|
||||
tristate "4096-Bit Addressable 1-Wire EEPROM with PIO (DS28E04-100)"
|
||||
select CRC16
|
||||
help
|
||||
If you enable this you will have the DS28E04-100
|
||||
chip support.
|
||||
|
||||
Say Y here if you want to use a 1-wire
|
||||
4kb EEPROM with PIO family device (DS28E04).
|
||||
|
||||
If you are unsure, say N.
|
||||
|
||||
config W1_SLAVE_BQ27000
|
||||
tristate "BQ27000 slave support"
|
||||
help
|
||||
Say Y here if you want to use a hdq
|
||||
bq27000 slave support.
|
||||
|
||||
endmenu
|
17
drivers/w1/slaves/Makefile
Normal file
17
drivers/w1/slaves/Makefile
Normal file
|
@ -0,0 +1,17 @@
|
|||
#
|
||||
# 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_DS2413) += w1_ds2413.o
|
||||
obj-$(CONFIG_W1_SLAVE_DS2406) += w1_ds2406.o
|
||||
obj-$(CONFIG_W1_SLAVE_DS2423) += w1_ds2423.o
|
||||
obj-$(CONFIG_W1_SLAVE_DS2431) += w1_ds2431.o
|
||||
obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o
|
||||
obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o
|
||||
obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o
|
||||
obj-$(CONFIG_W1_SLAVE_DS2781) += w1_ds2781.o
|
||||
obj-$(CONFIG_W1_SLAVE_BQ27000) += w1_bq27000.o
|
||||
obj-$(CONFIG_W1_SLAVE_DS28E04) += w1_ds28e04.o
|
117
drivers/w1/slaves/w1_bq27000.c
Normal file
117
drivers/w1/slaves/w1_bq27000.c
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 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 <linux/power/bq27x00_battery.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;
|
||||
|
||||
static int w1_bq27000_read(struct device *dev, unsigned int reg)
|
||||
{
|
||||
u8 val;
|
||||
struct w1_slave *sl = container_of(dev->parent, struct w1_slave, dev);
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
w1_write_8(sl->master, HDQ_CMD_READ | reg);
|
||||
val = w1_read_8(sl->master);
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static struct bq27000_platform_data bq27000_battery_info = {
|
||||
.read = w1_bq27000_read,
|
||||
.name = "bq27000-battery",
|
||||
};
|
||||
|
||||
static int w1_bq27000_add_slave(struct w1_slave *sl)
|
||||
{
|
||||
int ret;
|
||||
struct platform_device *pdev;
|
||||
|
||||
pdev = platform_device_alloc("bq27000-battery", -1);
|
||||
if (!pdev) {
|
||||
ret = -ENOMEM;
|
||||
return ret;
|
||||
}
|
||||
ret = platform_device_add_data(pdev,
|
||||
&bq27000_battery_info,
|
||||
sizeof(bq27000_battery_info));
|
||||
if (ret)
|
||||
goto pdev_add_failed;
|
||||
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_put(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");
|
168
drivers/w1/slaves/w1_ds2406.c
Normal file
168
drivers/w1/slaves/w1_ds2406.c
Normal file
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* w1_ds2406.c - w1 family 12 (DS2406) driver
|
||||
* based on w1_ds2413.c by Mariusz Bialonczyk <manio@skyboo.net>
|
||||
*
|
||||
* Copyright (c) 2014 Scott Alfter <scott@alfter.us>
|
||||
*
|
||||
* This source code is licensed under the GNU General Public License,
|
||||
* Version 2. See the file COPYING for more details.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/crc16.h>
|
||||
|
||||
#include "../w1.h"
|
||||
#include "../w1_int.h"
|
||||
#include "../w1_family.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Scott Alfter <scott@alfter.us>");
|
||||
MODULE_DESCRIPTION("w1 family 12 driver for DS2406 2 Pin IO");
|
||||
|
||||
#define W1_F12_FUNC_READ_STATUS 0xAA
|
||||
#define W1_F12_FUNC_WRITE_STATUS 0x55
|
||||
|
||||
static ssize_t w1_f12_read_state(
|
||||
struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
u8 w1_buf[6]={W1_F12_FUNC_READ_STATUS, 7, 0, 0, 0, 0};
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
u16 crc=0;
|
||||
int i;
|
||||
ssize_t rtnval=1;
|
||||
|
||||
if (off != 0)
|
||||
return 0;
|
||||
if (!buf)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
|
||||
if (w1_reset_select_slave(sl)) {
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
w1_write_block(sl->master, w1_buf, 3);
|
||||
w1_read_block(sl->master, w1_buf+3, 3);
|
||||
for (i=0; i<6; i++)
|
||||
crc=crc16_byte(crc, w1_buf[i]);
|
||||
if (crc==0xb001) /* good read? */
|
||||
*buf=((w1_buf[3]>>5)&3)|0x30;
|
||||
else
|
||||
rtnval=-EIO;
|
||||
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
|
||||
return rtnval;
|
||||
}
|
||||
|
||||
static ssize_t w1_f12_write_output(
|
||||
struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
u8 w1_buf[6]={W1_F12_FUNC_WRITE_STATUS, 7, 0, 0, 0, 0};
|
||||
u16 crc=0;
|
||||
int i;
|
||||
ssize_t rtnval=1;
|
||||
|
||||
if (count != 1 || off != 0)
|
||||
return -EFAULT;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
|
||||
if (w1_reset_select_slave(sl)) {
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
w1_buf[3] = (((*buf)&3)<<5)|0x1F;
|
||||
w1_write_block(sl->master, w1_buf, 4);
|
||||
w1_read_block(sl->master, w1_buf+4, 2);
|
||||
for (i=0; i<6; i++)
|
||||
crc=crc16_byte(crc, w1_buf[i]);
|
||||
if (crc==0xb001) /* good read? */
|
||||
w1_write_8(sl->master, 0xFF);
|
||||
else
|
||||
rtnval=-EIO;
|
||||
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
return rtnval;
|
||||
}
|
||||
|
||||
#define NB_SYSFS_BIN_FILES 2
|
||||
static struct bin_attribute w1_f12_sysfs_bin_files[NB_SYSFS_BIN_FILES] = {
|
||||
{
|
||||
.attr = {
|
||||
.name = "state",
|
||||
.mode = S_IRUGO,
|
||||
},
|
||||
.size = 1,
|
||||
.read = w1_f12_read_state,
|
||||
},
|
||||
{
|
||||
.attr = {
|
||||
.name = "output",
|
||||
.mode = S_IRUGO | S_IWUSR | S_IWGRP,
|
||||
},
|
||||
.size = 1,
|
||||
.write = w1_f12_write_output,
|
||||
}
|
||||
};
|
||||
|
||||
static int w1_f12_add_slave(struct w1_slave *sl)
|
||||
{
|
||||
int err = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NB_SYSFS_BIN_FILES && !err; ++i)
|
||||
err = sysfs_create_bin_file(
|
||||
&sl->dev.kobj,
|
||||
&(w1_f12_sysfs_bin_files[i]));
|
||||
if (err)
|
||||
while (--i >= 0)
|
||||
sysfs_remove_bin_file(&sl->dev.kobj,
|
||||
&(w1_f12_sysfs_bin_files[i]));
|
||||
return err;
|
||||
}
|
||||
|
||||
static void w1_f12_remove_slave(struct w1_slave *sl)
|
||||
{
|
||||
int i;
|
||||
for (i = NB_SYSFS_BIN_FILES - 1; i >= 0; --i)
|
||||
sysfs_remove_bin_file(&sl->dev.kobj,
|
||||
&(w1_f12_sysfs_bin_files[i]));
|
||||
}
|
||||
|
||||
static struct w1_family_ops w1_f12_fops = {
|
||||
.add_slave = w1_f12_add_slave,
|
||||
.remove_slave = w1_f12_remove_slave,
|
||||
};
|
||||
|
||||
static struct w1_family w1_family_12 = {
|
||||
.fid = W1_FAMILY_DS2406,
|
||||
.fops = &w1_f12_fops,
|
||||
};
|
||||
|
||||
static int __init w1_f12_init(void)
|
||||
{
|
||||
return w1_register_family(&w1_family_12);
|
||||
}
|
||||
|
||||
static void __exit w1_f12_exit(void)
|
||||
{
|
||||
w1_unregister_family(&w1_family_12);
|
||||
}
|
||||
|
||||
module_init(w1_f12_init);
|
||||
module_exit(w1_f12_exit);
|
366
drivers/w1/slaves/w1_ds2408.c
Normal file
366
drivers/w1/slaves/w1_ds2408.c
Normal file
|
@ -0,0 +1,366 @@
|
|||
/*
|
||||
* w1_ds2408.c - w1 family 29 (DS2408) driver
|
||||
*
|
||||
* Copyright (c) 2010 Jean-Francois Dagenais <dagenaisj@sonatest.com>
|
||||
*
|
||||
* This source code is licensed under the GNU General Public License,
|
||||
* Version 2. See the file COPYING for more details.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "../w1.h"
|
||||
#include "../w1_int.h"
|
||||
#include "../w1_family.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Jean-Francois Dagenais <dagenaisj@sonatest.com>");
|
||||
MODULE_DESCRIPTION("w1 family 29 driver for DS2408 8 Pin IO");
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2408));
|
||||
|
||||
|
||||
#define W1_F29_RETRIES 3
|
||||
|
||||
#define W1_F29_REG_LOGIG_STATE 0x88 /* R */
|
||||
#define W1_F29_REG_OUTPUT_LATCH_STATE 0x89 /* R */
|
||||
#define W1_F29_REG_ACTIVITY_LATCH_STATE 0x8A /* R */
|
||||
#define W1_F29_REG_COND_SEARCH_SELECT_MASK 0x8B /* RW */
|
||||
#define W1_F29_REG_COND_SEARCH_POL_SELECT 0x8C /* RW */
|
||||
#define W1_F29_REG_CONTROL_AND_STATUS 0x8D /* RW */
|
||||
|
||||
#define W1_F29_FUNC_READ_PIO_REGS 0xF0
|
||||
#define W1_F29_FUNC_CHANN_ACCESS_READ 0xF5
|
||||
#define W1_F29_FUNC_CHANN_ACCESS_WRITE 0x5A
|
||||
/* also used to write the control/status reg (0x8D): */
|
||||
#define W1_F29_FUNC_WRITE_COND_SEARCH_REG 0xCC
|
||||
#define W1_F29_FUNC_RESET_ACTIVITY_LATCHES 0xC3
|
||||
|
||||
#define W1_F29_SUCCESS_CONFIRM_BYTE 0xAA
|
||||
|
||||
static int _read_reg(struct w1_slave *sl, u8 address, unsigned char* buf)
|
||||
{
|
||||
u8 wrbuf[3];
|
||||
dev_dbg(&sl->dev,
|
||||
"Reading with slave: %p, reg addr: %0#4x, buff addr: %p",
|
||||
sl, (unsigned int)address, buf);
|
||||
|
||||
if (!buf)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
dev_dbg(&sl->dev, "mutex locked");
|
||||
|
||||
if (w1_reset_select_slave(sl)) {
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
wrbuf[0] = W1_F29_FUNC_READ_PIO_REGS;
|
||||
wrbuf[1] = address;
|
||||
wrbuf[2] = 0;
|
||||
w1_write_block(sl->master, wrbuf, 3);
|
||||
*buf = w1_read_8(sl->master);
|
||||
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
dev_dbg(&sl->dev, "mutex unlocked");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static ssize_t state_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf, loff_t off,
|
||||
size_t count)
|
||||
{
|
||||
dev_dbg(&kobj_to_w1_slave(kobj)->dev,
|
||||
"Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p",
|
||||
bin_attr->attr.name, kobj, (unsigned int)off, count, buf);
|
||||
if (count != 1 || off != 0)
|
||||
return -EFAULT;
|
||||
return _read_reg(kobj_to_w1_slave(kobj), W1_F29_REG_LOGIG_STATE, buf);
|
||||
}
|
||||
|
||||
static ssize_t output_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
dev_dbg(&kobj_to_w1_slave(kobj)->dev,
|
||||
"Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p",
|
||||
bin_attr->attr.name, kobj, (unsigned int)off, count, buf);
|
||||
if (count != 1 || off != 0)
|
||||
return -EFAULT;
|
||||
return _read_reg(kobj_to_w1_slave(kobj),
|
||||
W1_F29_REG_OUTPUT_LATCH_STATE, buf);
|
||||
}
|
||||
|
||||
static ssize_t activity_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
dev_dbg(&kobj_to_w1_slave(kobj)->dev,
|
||||
"Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p",
|
||||
bin_attr->attr.name, kobj, (unsigned int)off, count, buf);
|
||||
if (count != 1 || off != 0)
|
||||
return -EFAULT;
|
||||
return _read_reg(kobj_to_w1_slave(kobj),
|
||||
W1_F29_REG_ACTIVITY_LATCH_STATE, buf);
|
||||
}
|
||||
|
||||
static ssize_t cond_search_mask_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
dev_dbg(&kobj_to_w1_slave(kobj)->dev,
|
||||
"Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p",
|
||||
bin_attr->attr.name, kobj, (unsigned int)off, count, buf);
|
||||
if (count != 1 || off != 0)
|
||||
return -EFAULT;
|
||||
return _read_reg(kobj_to_w1_slave(kobj),
|
||||
W1_F29_REG_COND_SEARCH_SELECT_MASK, buf);
|
||||
}
|
||||
|
||||
static ssize_t cond_search_polarity_read(struct file *filp,
|
||||
struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
if (count != 1 || off != 0)
|
||||
return -EFAULT;
|
||||
return _read_reg(kobj_to_w1_slave(kobj),
|
||||
W1_F29_REG_COND_SEARCH_POL_SELECT, buf);
|
||||
}
|
||||
|
||||
static ssize_t status_control_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
if (count != 1 || off != 0)
|
||||
return -EFAULT;
|
||||
return _read_reg(kobj_to_w1_slave(kobj),
|
||||
W1_F29_REG_CONTROL_AND_STATUS, buf);
|
||||
}
|
||||
|
||||
static ssize_t output_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
u8 w1_buf[3];
|
||||
u8 readBack;
|
||||
unsigned int retries = W1_F29_RETRIES;
|
||||
|
||||
if (count != 1 || off != 0)
|
||||
return -EFAULT;
|
||||
|
||||
dev_dbg(&sl->dev, "locking mutex for write_output");
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
dev_dbg(&sl->dev, "mutex locked");
|
||||
|
||||
if (w1_reset_select_slave(sl))
|
||||
goto error;
|
||||
|
||||
while (retries--) {
|
||||
w1_buf[0] = W1_F29_FUNC_CHANN_ACCESS_WRITE;
|
||||
w1_buf[1] = *buf;
|
||||
w1_buf[2] = ~(*buf);
|
||||
w1_write_block(sl->master, w1_buf, 3);
|
||||
|
||||
readBack = w1_read_8(sl->master);
|
||||
|
||||
if (readBack != W1_F29_SUCCESS_CONFIRM_BYTE) {
|
||||
if (w1_reset_resume_command(sl->master))
|
||||
goto error;
|
||||
/* try again, the slave is ready for a command */
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_W1_SLAVE_DS2408_READBACK
|
||||
/* here the master could read another byte which
|
||||
would be the PIO reg (the actual pin logic state)
|
||||
since in this driver we don't know which pins are
|
||||
in and outs, there's no value to read the state and
|
||||
compare. with (*buf) so end this command abruptly: */
|
||||
if (w1_reset_resume_command(sl->master))
|
||||
goto error;
|
||||
|
||||
/* go read back the output latches */
|
||||
/* (the direct effect of the write above) */
|
||||
w1_buf[0] = W1_F29_FUNC_READ_PIO_REGS;
|
||||
w1_buf[1] = W1_F29_REG_OUTPUT_LATCH_STATE;
|
||||
w1_buf[2] = 0;
|
||||
w1_write_block(sl->master, w1_buf, 3);
|
||||
/* read the result of the READ_PIO_REGS command */
|
||||
if (w1_read_8(sl->master) == *buf)
|
||||
#endif
|
||||
{
|
||||
/* success! */
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
dev_dbg(&sl->dev,
|
||||
"mutex unlocked, retries:%d", retries);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
error:
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
dev_dbg(&sl->dev, "mutex unlocked in error, retries:%d", retries);
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writing to the activity file resets the activity latches.
|
||||
*/
|
||||
static ssize_t activity_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
unsigned int retries = W1_F29_RETRIES;
|
||||
|
||||
if (count != 1 || off != 0)
|
||||
return -EFAULT;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
|
||||
if (w1_reset_select_slave(sl))
|
||||
goto error;
|
||||
|
||||
while (retries--) {
|
||||
w1_write_8(sl->master, W1_F29_FUNC_RESET_ACTIVITY_LATCHES);
|
||||
if (w1_read_8(sl->master) == W1_F29_SUCCESS_CONFIRM_BYTE) {
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
return 1;
|
||||
}
|
||||
if (w1_reset_resume_command(sl->master))
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static ssize_t status_control_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
u8 w1_buf[4];
|
||||
unsigned int retries = W1_F29_RETRIES;
|
||||
|
||||
if (count != 1 || off != 0)
|
||||
return -EFAULT;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
|
||||
if (w1_reset_select_slave(sl))
|
||||
goto error;
|
||||
|
||||
while (retries--) {
|
||||
w1_buf[0] = W1_F29_FUNC_WRITE_COND_SEARCH_REG;
|
||||
w1_buf[1] = W1_F29_REG_CONTROL_AND_STATUS;
|
||||
w1_buf[2] = 0;
|
||||
w1_buf[3] = *buf;
|
||||
|
||||
w1_write_block(sl->master, w1_buf, 4);
|
||||
if (w1_reset_resume_command(sl->master))
|
||||
goto error;
|
||||
|
||||
w1_buf[0] = W1_F29_FUNC_READ_PIO_REGS;
|
||||
w1_buf[1] = W1_F29_REG_CONTROL_AND_STATUS;
|
||||
w1_buf[2] = 0;
|
||||
|
||||
w1_write_block(sl->master, w1_buf, 3);
|
||||
if (w1_read_8(sl->master) == *buf) {
|
||||
/* success! */
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
error:
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a special sequence we must do to ensure the P0 output is not stuck
|
||||
* in test mode. This is described in rev 2 of the ds2408's datasheet
|
||||
* (http://datasheets.maximintegrated.com/en/ds/DS2408.pdf) under
|
||||
* "APPLICATION INFORMATION/Power-up timing".
|
||||
*/
|
||||
static int w1_f29_disable_test_mode(struct w1_slave *sl)
|
||||
{
|
||||
int res;
|
||||
u8 magic[10] = {0x96, };
|
||||
u64 rn = le64_to_cpu(*((u64*)&sl->reg_num));
|
||||
|
||||
memcpy(&magic[1], &rn, 8);
|
||||
magic[9] = 0x3C;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
|
||||
res = w1_reset_bus(sl->master);
|
||||
if (res)
|
||||
goto out;
|
||||
w1_write_block(sl->master, magic, ARRAY_SIZE(magic));
|
||||
|
||||
res = w1_reset_bus(sl->master);
|
||||
out:
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
return res;
|
||||
}
|
||||
|
||||
static BIN_ATTR_RO(state, 1);
|
||||
static BIN_ATTR_RW(output, 1);
|
||||
static BIN_ATTR_RW(activity, 1);
|
||||
static BIN_ATTR_RO(cond_search_mask, 1);
|
||||
static BIN_ATTR_RO(cond_search_polarity, 1);
|
||||
static BIN_ATTR_RW(status_control, 1);
|
||||
|
||||
static struct bin_attribute *w1_f29_bin_attrs[] = {
|
||||
&bin_attr_state,
|
||||
&bin_attr_output,
|
||||
&bin_attr_activity,
|
||||
&bin_attr_cond_search_mask,
|
||||
&bin_attr_cond_search_polarity,
|
||||
&bin_attr_status_control,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group w1_f29_group = {
|
||||
.bin_attrs = w1_f29_bin_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *w1_f29_groups[] = {
|
||||
&w1_f29_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct w1_family_ops w1_f29_fops = {
|
||||
.add_slave = w1_f29_disable_test_mode,
|
||||
.groups = w1_f29_groups,
|
||||
};
|
||||
|
||||
static struct w1_family w1_family_29 = {
|
||||
.fid = W1_FAMILY_DS2408,
|
||||
.fops = &w1_f29_fops,
|
||||
};
|
||||
|
||||
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);
|
150
drivers/w1/slaves/w1_ds2413.c
Normal file
150
drivers/w1/slaves/w1_ds2413.c
Normal file
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* w1_ds2413.c - w1 family 3a (DS2413) driver
|
||||
* based on w1_ds2408.c by Jean-Francois Dagenais <dagenaisj@sonatest.com>
|
||||
*
|
||||
* Copyright (c) 2013 Mariusz Bialonczyk <manio@skyboo.net>
|
||||
*
|
||||
* This source code is licensed under the GNU General Public License,
|
||||
* Version 2. See the file COPYING for more details.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "../w1.h"
|
||||
#include "../w1_int.h"
|
||||
#include "../w1_family.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Mariusz Bialonczyk <manio@skyboo.net>");
|
||||
MODULE_DESCRIPTION("w1 family 3a driver for DS2413 2 Pin IO");
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2413));
|
||||
|
||||
#define W1_F3A_RETRIES 3
|
||||
#define W1_F3A_FUNC_PIO_ACCESS_READ 0xF5
|
||||
#define W1_F3A_FUNC_PIO_ACCESS_WRITE 0x5A
|
||||
#define W1_F3A_SUCCESS_CONFIRM_BYTE 0xAA
|
||||
|
||||
static ssize_t state_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf, loff_t off,
|
||||
size_t count)
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
dev_dbg(&sl->dev,
|
||||
"Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p",
|
||||
bin_attr->attr.name, kobj, (unsigned int)off, count, buf);
|
||||
|
||||
if (off != 0)
|
||||
return 0;
|
||||
if (!buf)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
dev_dbg(&sl->dev, "mutex locked");
|
||||
|
||||
if (w1_reset_select_slave(sl)) {
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
w1_write_8(sl->master, W1_F3A_FUNC_PIO_ACCESS_READ);
|
||||
*buf = w1_read_8(sl->master);
|
||||
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
dev_dbg(&sl->dev, "mutex unlocked");
|
||||
|
||||
/* check for correct complement */
|
||||
if ((*buf & 0x0F) != ((~*buf >> 4) & 0x0F))
|
||||
return -EIO;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
static BIN_ATTR_RO(state, 1);
|
||||
|
||||
static ssize_t output_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
u8 w1_buf[3];
|
||||
unsigned int retries = W1_F3A_RETRIES;
|
||||
|
||||
if (count != 1 || off != 0)
|
||||
return -EFAULT;
|
||||
|
||||
dev_dbg(&sl->dev, "locking mutex for write_output");
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
dev_dbg(&sl->dev, "mutex locked");
|
||||
|
||||
if (w1_reset_select_slave(sl))
|
||||
goto error;
|
||||
|
||||
/* according to the DS2413 datasheet the most significant 6 bits
|
||||
should be set to "1"s, so do it now */
|
||||
*buf = *buf | 0xFC;
|
||||
|
||||
while (retries--) {
|
||||
w1_buf[0] = W1_F3A_FUNC_PIO_ACCESS_WRITE;
|
||||
w1_buf[1] = *buf;
|
||||
w1_buf[2] = ~(*buf);
|
||||
w1_write_block(sl->master, w1_buf, 3);
|
||||
|
||||
if (w1_read_8(sl->master) == W1_F3A_SUCCESS_CONFIRM_BYTE) {
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
dev_dbg(&sl->dev, "mutex unlocked, retries:%d", retries);
|
||||
return 1;
|
||||
}
|
||||
if (w1_reset_resume_command(sl->master))
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
dev_dbg(&sl->dev, "mutex unlocked in error, retries:%d", retries);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static BIN_ATTR(output, S_IRUGO | S_IWUSR | S_IWGRP, NULL, output_write, 1);
|
||||
|
||||
static struct bin_attribute *w1_f3a_bin_attrs[] = {
|
||||
&bin_attr_state,
|
||||
&bin_attr_output,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group w1_f3a_group = {
|
||||
.bin_attrs = w1_f3a_bin_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *w1_f3a_groups[] = {
|
||||
&w1_f3a_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct w1_family_ops w1_f3a_fops = {
|
||||
.groups = w1_f3a_groups,
|
||||
};
|
||||
|
||||
static struct w1_family w1_family_3a = {
|
||||
.fid = W1_FAMILY_DS2413,
|
||||
.fops = &w1_f3a_fops,
|
||||
};
|
||||
|
||||
static int __init w1_f3a_init(void)
|
||||
{
|
||||
return w1_register_family(&w1_family_3a);
|
||||
}
|
||||
|
||||
static void __exit w1_f3a_exit(void)
|
||||
{
|
||||
w1_unregister_family(&w1_family_3a);
|
||||
}
|
||||
|
||||
module_init(w1_f3a_init);
|
||||
module_exit(w1_f3a_exit);
|
158
drivers/w1/slaves/w1_ds2423.c
Normal file
158
drivers/w1/slaves/w1_ds2423.c
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* 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_slave_show(struct device *device,
|
||||
struct device_attribute *attr, char *out_buf)
|
||||
{
|
||||
struct w1_slave *sl = dev_to_w1_slave(device);
|
||||
struct w1_master *dev = sl->master;
|
||||
u8 rbuf[COUNTER_COUNT * READ_BYTE_COUNT];
|
||||
u8 wrbuf[3];
|
||||
int rom_addr;
|
||||
int read_byte_count;
|
||||
int result;
|
||||
ssize_t c;
|
||||
int ii;
|
||||
int p;
|
||||
int crc;
|
||||
|
||||
c = PAGE_SIZE;
|
||||
rom_addr = (12 << 5) + 31;
|
||||
wrbuf[0] = 0xA5;
|
||||
wrbuf[1] = rom_addr & 0xFF;
|
||||
wrbuf[2] = rom_addr >> 8;
|
||||
mutex_lock(&dev->bus_mutex);
|
||||
if (!w1_reset_select_slave(sl)) {
|
||||
w1_write_block(dev, wrbuf, 3);
|
||||
read_byte_count = 0;
|
||||
for (p = 0; p < 4; p++) {
|
||||
/*
|
||||
* 1 byte for first bytes in ram page read
|
||||
* 4 bytes for counter
|
||||
* 4 bytes for zero bits
|
||||
* 2 bytes for crc
|
||||
* 31 remaining bytes from the ram page
|
||||
*/
|
||||
read_byte_count += w1_read_block(dev,
|
||||
rbuf + (p * READ_BYTE_COUNT), READ_BYTE_COUNT);
|
||||
for (ii = 0; ii < READ_BYTE_COUNT; ++ii)
|
||||
c -= snprintf(out_buf + PAGE_SIZE - c,
|
||||
c, "%02x ",
|
||||
rbuf[(p * READ_BYTE_COUNT) + ii]);
|
||||
if (read_byte_count != (p + 1) * READ_BYTE_COUNT) {
|
||||
dev_warn(device,
|
||||
"w1_counter_read() returned %u bytes "
|
||||
"instead of %d bytes wanted.\n",
|
||||
read_byte_count,
|
||||
READ_BYTE_COUNT);
|
||||
c -= snprintf(out_buf + PAGE_SIZE - c,
|
||||
c, "crc=NO\n");
|
||||
} else {
|
||||
if (p == 0) {
|
||||
crc = crc16(CRC16_INIT, wrbuf, 3);
|
||||
crc = crc16(crc, rbuf, 11);
|
||||
} else {
|
||||
/*
|
||||
* DS2423 calculates crc from all bytes
|
||||
* read after the previous crc bytes.
|
||||
*/
|
||||
crc = crc16(CRC16_INIT,
|
||||
(rbuf + 11) +
|
||||
((p - 1) * READ_BYTE_COUNT),
|
||||
READ_BYTE_COUNT);
|
||||
}
|
||||
if (crc == CRC16_VALID) {
|
||||
result = 0;
|
||||
for (ii = 4; ii > 0; ii--) {
|
||||
result <<= 8;
|
||||
result |= rbuf[(p *
|
||||
READ_BYTE_COUNT) + ii];
|
||||
}
|
||||
c -= snprintf(out_buf + PAGE_SIZE - c,
|
||||
c, "crc=YES c=%d\n", result);
|
||||
} else {
|
||||
c -= snprintf(out_buf + PAGE_SIZE - c,
|
||||
c, "crc=NO\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c -= snprintf(out_buf + PAGE_SIZE - c, c, "Connection error");
|
||||
}
|
||||
mutex_unlock(&dev->bus_mutex);
|
||||
return PAGE_SIZE - c;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(w1_slave);
|
||||
|
||||
static struct attribute *w1_f1d_attrs[] = {
|
||||
&dev_attr_w1_slave.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(w1_f1d);
|
||||
|
||||
static struct w1_family_ops w1_f1d_fops = {
|
||||
.groups = w1_f1d_groups,
|
||||
};
|
||||
|
||||
static struct w1_family w1_family_1d = {
|
||||
.fid = W1_COUNTER_DS2423,
|
||||
.fops = &w1_f1d_fops,
|
||||
};
|
||||
|
||||
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");
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_COUNTER_DS2423));
|
308
drivers/w1/slaves/w1_ds2431.c
Normal file
308
drivers/w1/slaves/w1_ds2431.c
Normal file
|
@ -0,0 +1,308 @@
|
|||
/*
|
||||
* 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 eeprom_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
int todo = count;
|
||||
|
||||
count = w1_f2d_fix_count(off, count, W1_F2D_EEPROM_SIZE);
|
||||
if (count == 0)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
|
||||
/* read directly from the EEPROM in chunks of W1_F2D_READ_MAXLEN */
|
||||
while (todo > 0) {
|
||||
int block_read;
|
||||
|
||||
if (todo >= W1_F2D_READ_MAXLEN)
|
||||
block_read = W1_F2D_READ_MAXLEN;
|
||||
else
|
||||
block_read = todo;
|
||||
|
||||
if (w1_f2d_readblock(sl, off, block_read, buf) < 0)
|
||||
count = -EIO;
|
||||
|
||||
todo -= W1_F2D_READ_MAXLEN;
|
||||
buf += W1_F2D_READ_MAXLEN;
|
||||
off += W1_F2D_READ_MAXLEN;
|
||||
}
|
||||
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Writes to the scratchpad and reads it back for verification.
|
||||
* Then copies the scratchpad to EEPROM.
|
||||
* The data must be aligned at W1_F2D_SCRATCH_SIZE bytes and
|
||||
* must be W1_F2D_SCRATCH_SIZE bytes long.
|
||||
* The master must be locked.
|
||||
*
|
||||
* @param sl The slave structure
|
||||
* @param addr Address for the write
|
||||
* @param len length must be <= (W1_F2D_PAGE_SIZE - (addr & W1_F2D_PAGE_MASK))
|
||||
* @param data The data to write
|
||||
* @return 0=Success -1=failure
|
||||
*/
|
||||
static int w1_f2d_write(struct w1_slave *sl, int addr, int len, const u8 *data)
|
||||
{
|
||||
int tries = W1_F2D_READ_RETRIES;
|
||||
u8 wrbuf[4];
|
||||
u8 rdbuf[W1_F2D_SCRATCH_SIZE + 3];
|
||||
u8 es = (addr + len - 1) % W1_F2D_SCRATCH_SIZE;
|
||||
|
||||
retry:
|
||||
|
||||
/* Write the data to the scratchpad */
|
||||
if (w1_reset_select_slave(sl))
|
||||
return -1;
|
||||
|
||||
wrbuf[0] = W1_F2D_WRITE_SCRATCH;
|
||||
wrbuf[1] = addr & 0xff;
|
||||
wrbuf[2] = addr >> 8;
|
||||
|
||||
w1_write_block(sl->master, wrbuf, 3);
|
||||
w1_write_block(sl->master, data, len);
|
||||
|
||||
/* Read the scratchpad and verify */
|
||||
if (w1_reset_select_slave(sl))
|
||||
return -1;
|
||||
|
||||
w1_write_8(sl->master, W1_F2D_READ_SCRATCH);
|
||||
w1_read_block(sl->master, rdbuf, len + 3);
|
||||
|
||||
/* Compare what was read against the data written */
|
||||
if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) ||
|
||||
(rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0)) {
|
||||
|
||||
if (--tries)
|
||||
goto retry;
|
||||
|
||||
dev_err(&sl->dev,
|
||||
"could not write to eeprom, scratchpad compare failed %d times\n",
|
||||
W1_F2D_READ_RETRIES);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Copy the scratchpad to EEPROM */
|
||||
if (w1_reset_select_slave(sl))
|
||||
return -1;
|
||||
|
||||
wrbuf[0] = W1_F2D_COPY_SCRATCH;
|
||||
wrbuf[3] = es;
|
||||
w1_write_block(sl->master, wrbuf, 4);
|
||||
|
||||
/* Sleep for tprog ms to wait for the write to complete */
|
||||
msleep(W1_F2D_TPROG_MS);
|
||||
|
||||
/* Reset the bus to wake up the EEPROM */
|
||||
w1_reset_bus(sl->master);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t eeprom_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
int addr, len;
|
||||
int copy;
|
||||
|
||||
count = w1_f2d_fix_count(off, count, W1_F2D_EEPROM_SIZE);
|
||||
if (count == 0)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
|
||||
/* Can only write data in blocks of the size of the scratchpad */
|
||||
addr = off;
|
||||
len = count;
|
||||
while (len > 0) {
|
||||
|
||||
/* if len too short or addr not aligned */
|
||||
if (len < W1_F2D_SCRATCH_SIZE || addr & W1_F2D_SCRATCH_MASK) {
|
||||
char tmp[W1_F2D_SCRATCH_SIZE];
|
||||
|
||||
/* read the block and update the parts to be written */
|
||||
if (w1_f2d_readblock(sl, addr & ~W1_F2D_SCRATCH_MASK,
|
||||
W1_F2D_SCRATCH_SIZE, tmp)) {
|
||||
count = -EIO;
|
||||
goto out_up;
|
||||
}
|
||||
|
||||
/* copy at most to the boundary of the PAGE or len */
|
||||
copy = W1_F2D_SCRATCH_SIZE -
|
||||
(addr & W1_F2D_SCRATCH_MASK);
|
||||
|
||||
if (copy > len)
|
||||
copy = len;
|
||||
|
||||
memcpy(&tmp[addr & W1_F2D_SCRATCH_MASK], buf, copy);
|
||||
if (w1_f2d_write(sl, addr & ~W1_F2D_SCRATCH_MASK,
|
||||
W1_F2D_SCRATCH_SIZE, tmp) < 0) {
|
||||
count = -EIO;
|
||||
goto out_up;
|
||||
}
|
||||
} else {
|
||||
|
||||
copy = W1_F2D_SCRATCH_SIZE;
|
||||
if (w1_f2d_write(sl, addr, copy, buf) < 0) {
|
||||
count = -EIO;
|
||||
goto out_up;
|
||||
}
|
||||
}
|
||||
buf += copy;
|
||||
addr += copy;
|
||||
len -= copy;
|
||||
}
|
||||
|
||||
out_up:
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static BIN_ATTR_RW(eeprom, W1_F2D_EEPROM_SIZE);
|
||||
|
||||
static struct bin_attribute *w1_f2d_bin_attrs[] = {
|
||||
&bin_attr_eeprom,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group w1_f2d_group = {
|
||||
.bin_attrs = w1_f2d_bin_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *w1_f2d_groups[] = {
|
||||
&w1_f2d_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct w1_family_ops w1_f2d_fops = {
|
||||
.groups = w1_f2d_groups,
|
||||
};
|
||||
|
||||
static struct w1_family w1_family_2d = {
|
||||
.fid = W1_EEPROM_DS2431,
|
||||
.fops = &w1_f2d_fops,
|
||||
};
|
||||
|
||||
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");
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_EEPROM_DS2431));
|
320
drivers/w1/slaves/w1_ds2433.c
Normal file
320
drivers/w1/slaves/w1_ds2433.c
Normal file
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
* 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");
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_EEPROM_DS2433));
|
||||
|
||||
#define W1_EEPROM_SIZE 512
|
||||
#define W1_PAGE_COUNT 16
|
||||
#define W1_PAGE_SIZE 32
|
||||
#define W1_PAGE_BITS 5
|
||||
#define W1_PAGE_MASK 0x1F
|
||||
|
||||
#define W1_F23_TIME 300
|
||||
|
||||
#define W1_F23_READ_EEPROM 0xF0
|
||||
#define W1_F23_WRITE_SCRATCH 0x0F
|
||||
#define W1_F23_READ_SCRATCH 0xAA
|
||||
#define W1_F23_COPY_SCRATCH 0x55
|
||||
|
||||
struct w1_f23_data {
|
||||
u8 memory[W1_EEPROM_SIZE];
|
||||
u32 validcrc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check the file size bounds and adjusts count as needed.
|
||||
* This would not be needed if the file size didn't reset to 0 after a write.
|
||||
*/
|
||||
static inline size_t w1_f23_fix_count(loff_t off, size_t count, size_t size)
|
||||
{
|
||||
if (off > size)
|
||||
return 0;
|
||||
|
||||
if ((off + count) > size)
|
||||
return (size - off);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_W1_SLAVE_DS2433_CRC
|
||||
static int w1_f23_refresh_block(struct w1_slave *sl, struct w1_f23_data *data,
|
||||
int block)
|
||||
{
|
||||
u8 wrbuf[3];
|
||||
int off = block * W1_PAGE_SIZE;
|
||||
|
||||
if (data->validcrc & (1 << block))
|
||||
return 0;
|
||||
|
||||
if (w1_reset_select_slave(sl)) {
|
||||
data->validcrc = 0;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
wrbuf[0] = W1_F23_READ_EEPROM;
|
||||
wrbuf[1] = off & 0xff;
|
||||
wrbuf[2] = off >> 8;
|
||||
w1_write_block(sl->master, wrbuf, 3);
|
||||
w1_read_block(sl->master, &data->memory[off], W1_PAGE_SIZE);
|
||||
|
||||
/* cache the block if the CRC is valid */
|
||||
if (crc16(CRC16_INIT, &data->memory[off], W1_PAGE_SIZE) == CRC16_VALID)
|
||||
data->validcrc |= (1 << block);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_W1_SLAVE_DS2433_CRC */
|
||||
|
||||
static ssize_t eeprom_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
#ifdef CONFIG_W1_SLAVE_DS2433_CRC
|
||||
struct w1_f23_data *data = sl->family_data;
|
||||
int i, min_page, max_page;
|
||||
#else
|
||||
u8 wrbuf[3];
|
||||
#endif
|
||||
|
||||
if ((count = w1_f23_fix_count(off, count, W1_EEPROM_SIZE)) == 0)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
|
||||
#ifdef CONFIG_W1_SLAVE_DS2433_CRC
|
||||
|
||||
min_page = (off >> W1_PAGE_BITS);
|
||||
max_page = (off + count - 1) >> W1_PAGE_BITS;
|
||||
for (i = min_page; i <= max_page; i++) {
|
||||
if (w1_f23_refresh_block(sl, data, i)) {
|
||||
count = -EIO;
|
||||
goto out_up;
|
||||
}
|
||||
}
|
||||
memcpy(buf, &data->memory[off], count);
|
||||
|
||||
#else /* CONFIG_W1_SLAVE_DS2433_CRC */
|
||||
|
||||
/* read directly from the EEPROM */
|
||||
if (w1_reset_select_slave(sl)) {
|
||||
count = -EIO;
|
||||
goto out_up;
|
||||
}
|
||||
|
||||
wrbuf[0] = W1_F23_READ_EEPROM;
|
||||
wrbuf[1] = off & 0xff;
|
||||
wrbuf[2] = off >> 8;
|
||||
w1_write_block(sl->master, wrbuf, 3);
|
||||
w1_read_block(sl->master, buf, count);
|
||||
|
||||
#endif /* CONFIG_W1_SLAVE_DS2433_CRC */
|
||||
|
||||
out_up:
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to the scratchpad and reads it back for verification.
|
||||
* Then copies the scratchpad to EEPROM.
|
||||
* The data must be on one page.
|
||||
* The master must be locked.
|
||||
*
|
||||
* @param sl The slave structure
|
||||
* @param addr Address for the write
|
||||
* @param len length must be <= (W1_PAGE_SIZE - (addr & W1_PAGE_MASK))
|
||||
* @param data The data to write
|
||||
* @return 0=Success -1=failure
|
||||
*/
|
||||
static int w1_f23_write(struct w1_slave *sl, int addr, int len, const u8 *data)
|
||||
{
|
||||
#ifdef CONFIG_W1_SLAVE_DS2433_CRC
|
||||
struct w1_f23_data *f23 = sl->family_data;
|
||||
#endif
|
||||
u8 wrbuf[4];
|
||||
u8 rdbuf[W1_PAGE_SIZE + 3];
|
||||
u8 es = (addr + len - 1) & 0x1f;
|
||||
|
||||
/* Write the data to the scratchpad */
|
||||
if (w1_reset_select_slave(sl))
|
||||
return -1;
|
||||
|
||||
wrbuf[0] = W1_F23_WRITE_SCRATCH;
|
||||
wrbuf[1] = addr & 0xff;
|
||||
wrbuf[2] = addr >> 8;
|
||||
|
||||
w1_write_block(sl->master, wrbuf, 3);
|
||||
w1_write_block(sl->master, data, len);
|
||||
|
||||
/* Read the scratchpad and verify */
|
||||
if (w1_reset_select_slave(sl))
|
||||
return -1;
|
||||
|
||||
w1_write_8(sl->master, W1_F23_READ_SCRATCH);
|
||||
w1_read_block(sl->master, rdbuf, len + 3);
|
||||
|
||||
/* Compare what was read against the data written */
|
||||
if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) ||
|
||||
(rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0))
|
||||
return -1;
|
||||
|
||||
/* Copy the scratchpad to EEPROM */
|
||||
if (w1_reset_select_slave(sl))
|
||||
return -1;
|
||||
|
||||
wrbuf[0] = W1_F23_COPY_SCRATCH;
|
||||
wrbuf[3] = es;
|
||||
w1_write_block(sl->master, wrbuf, 4);
|
||||
|
||||
/* Sleep for 5 ms to wait for the write to complete */
|
||||
msleep(5);
|
||||
|
||||
/* Reset the bus to wake up the EEPROM (this may not be needed) */
|
||||
w1_reset_bus(sl->master);
|
||||
#ifdef CONFIG_W1_SLAVE_DS2433_CRC
|
||||
f23->validcrc &= ~(1 << (addr >> W1_PAGE_BITS));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t eeprom_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
int addr, len, idx;
|
||||
|
||||
if ((count = w1_f23_fix_count(off, count, W1_EEPROM_SIZE)) == 0)
|
||||
return 0;
|
||||
|
||||
#ifdef CONFIG_W1_SLAVE_DS2433_CRC
|
||||
/* can only write full blocks in cached mode */
|
||||
if ((off & W1_PAGE_MASK) || (count & W1_PAGE_MASK)) {
|
||||
dev_err(&sl->dev, "invalid offset/count off=%d cnt=%zd\n",
|
||||
(int)off, count);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* make sure the block CRCs are valid */
|
||||
for (idx = 0; idx < count; idx += W1_PAGE_SIZE) {
|
||||
if (crc16(CRC16_INIT, &buf[idx], W1_PAGE_SIZE) != CRC16_VALID) {
|
||||
dev_err(&sl->dev, "bad CRC at offset %d\n", (int)off);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_W1_SLAVE_DS2433_CRC */
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
|
||||
/* Can only write data to one page at a time */
|
||||
idx = 0;
|
||||
while (idx < count) {
|
||||
addr = off + idx;
|
||||
len = W1_PAGE_SIZE - (addr & W1_PAGE_MASK);
|
||||
if (len > (count - idx))
|
||||
len = count - idx;
|
||||
|
||||
if (w1_f23_write(sl, addr, len, &buf[idx]) < 0) {
|
||||
count = -EIO;
|
||||
goto out_up;
|
||||
}
|
||||
idx += len;
|
||||
}
|
||||
|
||||
out_up:
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static BIN_ATTR_RW(eeprom, W1_EEPROM_SIZE);
|
||||
|
||||
static struct bin_attribute *w1_f23_bin_attributes[] = {
|
||||
&bin_attr_eeprom,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group w1_f23_group = {
|
||||
.bin_attrs = w1_f23_bin_attributes,
|
||||
};
|
||||
|
||||
static const struct attribute_group *w1_f23_groups[] = {
|
||||
&w1_f23_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int w1_f23_add_slave(struct w1_slave *sl)
|
||||
{
|
||||
#ifdef CONFIG_W1_SLAVE_DS2433_CRC
|
||||
struct w1_f23_data *data;
|
||||
|
||||
data = kzalloc(sizeof(struct w1_f23_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
sl->family_data = data;
|
||||
|
||||
#endif /* CONFIG_W1_SLAVE_DS2433_CRC */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void w1_f23_remove_slave(struct w1_slave *sl)
|
||||
{
|
||||
#ifdef CONFIG_W1_SLAVE_DS2433_CRC
|
||||
kfree(sl->family_data);
|
||||
sl->family_data = NULL;
|
||||
#endif /* CONFIG_W1_SLAVE_DS2433_CRC */
|
||||
}
|
||||
|
||||
static struct w1_family_ops w1_f23_fops = {
|
||||
.add_slave = w1_f23_add_slave,
|
||||
.remove_slave = w1_f23_remove_slave,
|
||||
.groups = w1_f23_groups,
|
||||
};
|
||||
|
||||
static struct w1_family w1_family_23 = {
|
||||
.fid = W1_EEPROM_DS2433,
|
||||
.fops = &w1_f23_fops,
|
||||
};
|
||||
|
||||
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);
|
206
drivers/w1/slaves/w1_ds2760.c
Normal file
206
drivers/w1/slaves/w1_ds2760.c
Normal file
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* 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->bus_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->bus_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->bus_mutex);
|
||||
|
||||
if (w1_reset_select_slave(sl) == 0) {
|
||||
w1_write_8(sl->master, cmd);
|
||||
w1_write_8(sl->master, addr);
|
||||
}
|
||||
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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_slave_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
return w1_ds2760_read(dev, buf, off, count);
|
||||
}
|
||||
|
||||
static BIN_ATTR_RO(w1_slave, DS2760_DATA_SIZE);
|
||||
|
||||
static struct bin_attribute *w1_ds2760_bin_attrs[] = {
|
||||
&bin_attr_w1_slave,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group w1_ds2760_group = {
|
||||
.bin_attrs = w1_ds2760_bin_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *w1_ds2760_groups[] = {
|
||||
&w1_ds2760_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static DEFINE_IDA(bat_ida);
|
||||
|
||||
static int w1_ds2760_add_slave(struct w1_slave *sl)
|
||||
{
|
||||
int ret;
|
||||
int id;
|
||||
struct platform_device *pdev;
|
||||
|
||||
id = ida_simple_get(&bat_ida, 0, 0, GFP_KERNEL);
|
||||
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;
|
||||
|
||||
dev_set_drvdata(&sl->dev, pdev);
|
||||
|
||||
goto success;
|
||||
|
||||
pdev_add_failed:
|
||||
platform_device_put(pdev);
|
||||
pdev_alloc_failed:
|
||||
ida_simple_remove(&bat_ida, 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);
|
||||
ida_simple_remove(&bat_ida, id);
|
||||
}
|
||||
|
||||
static struct w1_family_ops w1_ds2760_fops = {
|
||||
.add_slave = w1_ds2760_add_slave,
|
||||
.remove_slave = w1_ds2760_remove_slave,
|
||||
.groups = w1_ds2760_groups,
|
||||
};
|
||||
|
||||
static struct w1_family w1_ds2760_family = {
|
||||
.fid = W1_FAMILY_DS2760,
|
||||
.fops = &w1_ds2760_fops,
|
||||
};
|
||||
|
||||
static int __init w1_ds2760_init(void)
|
||||
{
|
||||
pr_info("1-Wire driver for the DS2760 battery monitor chip - (c) 2004-2005, Szabolcs Gyurko\n");
|
||||
ida_init(&bat_ida);
|
||||
return w1_register_family(&w1_ds2760_family);
|
||||
}
|
||||
|
||||
static void __exit w1_ds2760_exit(void)
|
||||
{
|
||||
w1_unregister_family(&w1_ds2760_family);
|
||||
ida_destroy(&bat_ida);
|
||||
}
|
||||
|
||||
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");
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2760));
|
57
drivers/w1/slaves/w1_ds2760.h
Normal file
57
drivers/w1/slaves/w1_ds2760.h
Normal file
|
@ -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__ */
|
191
drivers/w1/slaves/w1_ds2780.c
Normal file
191
drivers/w1/slaves/w1_ds2780.c
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* 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->bus_mutex);
|
||||
|
||||
ret = w1_ds2780_do_io(dev, buf, addr, count, io);
|
||||
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(w1_ds2780_io);
|
||||
|
||||
int w1_ds2780_eeprom_cmd(struct device *dev, int addr, int cmd)
|
||||
{
|
||||
struct w1_slave *sl = container_of(dev, struct w1_slave, dev);
|
||||
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
|
||||
if (w1_reset_select_slave(sl) == 0) {
|
||||
w1_write_8(sl->master, cmd);
|
||||
w1_write_8(sl->master, addr);
|
||||
}
|
||||
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(w1_ds2780_eeprom_cmd);
|
||||
|
||||
static ssize_t w1_slave_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
return w1_ds2780_io(dev, buf, off, count, 0);
|
||||
}
|
||||
|
||||
static BIN_ATTR_RO(w1_slave, DS2780_DATA_SIZE);
|
||||
|
||||
static struct bin_attribute *w1_ds2780_bin_attrs[] = {
|
||||
&bin_attr_w1_slave,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group w1_ds2780_group = {
|
||||
.bin_attrs = w1_ds2780_bin_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *w1_ds2780_groups[] = {
|
||||
&w1_ds2780_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static DEFINE_IDA(bat_ida);
|
||||
|
||||
static int w1_ds2780_add_slave(struct w1_slave *sl)
|
||||
{
|
||||
int ret;
|
||||
int id;
|
||||
struct platform_device *pdev;
|
||||
|
||||
id = ida_simple_get(&bat_ida, 0, 0, GFP_KERNEL);
|
||||
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;
|
||||
|
||||
dev_set_drvdata(&sl->dev, pdev);
|
||||
|
||||
return 0;
|
||||
|
||||
pdev_add_failed:
|
||||
platform_device_put(pdev);
|
||||
pdev_alloc_failed:
|
||||
ida_simple_remove(&bat_ida, 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);
|
||||
ida_simple_remove(&bat_ida, id);
|
||||
}
|
||||
|
||||
static struct w1_family_ops w1_ds2780_fops = {
|
||||
.add_slave = w1_ds2780_add_slave,
|
||||
.remove_slave = w1_ds2780_remove_slave,
|
||||
.groups = w1_ds2780_groups,
|
||||
};
|
||||
|
||||
static struct w1_family w1_ds2780_family = {
|
||||
.fid = W1_FAMILY_DS2780,
|
||||
.fops = &w1_ds2780_fops,
|
||||
};
|
||||
|
||||
static int __init w1_ds2780_init(void)
|
||||
{
|
||||
ida_init(&bat_ida);
|
||||
return w1_register_family(&w1_ds2780_family);
|
||||
}
|
||||
|
||||
static void __exit w1_ds2780_exit(void)
|
||||
{
|
||||
w1_unregister_family(&w1_ds2780_family);
|
||||
ida_destroy(&bat_ida);
|
||||
}
|
||||
|
||||
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");
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2780));
|
129
drivers/w1/slaves/w1_ds2780.h
Normal file
129
drivers/w1/slaves/w1_ds2780.h
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 1-Wire implementation for the ds2780 chip
|
||||
*
|
||||
* Copyright (C) 2010 Indesign, LLC
|
||||
*
|
||||
* Author: Clifton Barnes <cabarnes@indesign-llc.com>
|
||||
*
|
||||
* Based on w1-ds2760 driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _W1_DS2780_H
|
||||
#define _W1_DS2780_H
|
||||
|
||||
/* Function commands */
|
||||
#define W1_DS2780_READ_DATA 0x69
|
||||
#define W1_DS2780_WRITE_DATA 0x6C
|
||||
#define W1_DS2780_COPY_DATA 0x48
|
||||
#define W1_DS2780_RECALL_DATA 0xB8
|
||||
#define W1_DS2780_LOCK 0x6A
|
||||
|
||||
/* Register map */
|
||||
/* Register 0x00 Reserved */
|
||||
#define DS2780_STATUS_REG 0x01
|
||||
#define DS2780_RAAC_MSB_REG 0x02
|
||||
#define DS2780_RAAC_LSB_REG 0x03
|
||||
#define DS2780_RSAC_MSB_REG 0x04
|
||||
#define DS2780_RSAC_LSB_REG 0x05
|
||||
#define DS2780_RARC_REG 0x06
|
||||
#define DS2780_RSRC_REG 0x07
|
||||
#define DS2780_IAVG_MSB_REG 0x08
|
||||
#define DS2780_IAVG_LSB_REG 0x09
|
||||
#define DS2780_TEMP_MSB_REG 0x0A
|
||||
#define DS2780_TEMP_LSB_REG 0x0B
|
||||
#define DS2780_VOLT_MSB_REG 0x0C
|
||||
#define DS2780_VOLT_LSB_REG 0x0D
|
||||
#define DS2780_CURRENT_MSB_REG 0x0E
|
||||
#define DS2780_CURRENT_LSB_REG 0x0F
|
||||
#define DS2780_ACR_MSB_REG 0x10
|
||||
#define DS2780_ACR_LSB_REG 0x11
|
||||
#define DS2780_ACRL_MSB_REG 0x12
|
||||
#define DS2780_ACRL_LSB_REG 0x13
|
||||
#define DS2780_AS_REG 0x14
|
||||
#define DS2780_SFR_REG 0x15
|
||||
#define DS2780_FULL_MSB_REG 0x16
|
||||
#define DS2780_FULL_LSB_REG 0x17
|
||||
#define DS2780_AE_MSB_REG 0x18
|
||||
#define DS2780_AE_LSB_REG 0x19
|
||||
#define DS2780_SE_MSB_REG 0x1A
|
||||
#define DS2780_SE_LSB_REG 0x1B
|
||||
/* Register 0x1C - 0x1E Reserved */
|
||||
#define DS2780_EEPROM_REG 0x1F
|
||||
#define DS2780_EEPROM_BLOCK0_START 0x20
|
||||
/* Register 0x20 - 0x2F User EEPROM */
|
||||
#define DS2780_EEPROM_BLOCK0_END 0x2F
|
||||
/* Register 0x30 - 0x5F Reserved */
|
||||
#define DS2780_EEPROM_BLOCK1_START 0x60
|
||||
#define DS2780_CONTROL_REG 0x60
|
||||
#define DS2780_AB_REG 0x61
|
||||
#define DS2780_AC_MSB_REG 0x62
|
||||
#define DS2780_AC_LSB_REG 0x63
|
||||
#define DS2780_VCHG_REG 0x64
|
||||
#define DS2780_IMIN_REG 0x65
|
||||
#define DS2780_VAE_REG 0x66
|
||||
#define DS2780_IAE_REG 0x67
|
||||
#define DS2780_AE_40_REG 0x68
|
||||
#define DS2780_RSNSP_REG 0x69
|
||||
#define DS2780_FULL_40_MSB_REG 0x6A
|
||||
#define DS2780_FULL_40_LSB_REG 0x6B
|
||||
#define DS2780_FULL_3040_SLOPE_REG 0x6C
|
||||
#define DS2780_FULL_2030_SLOPE_REG 0x6D
|
||||
#define DS2780_FULL_1020_SLOPE_REG 0x6E
|
||||
#define DS2780_FULL_0010_SLOPE_REG 0x6F
|
||||
#define DS2780_AE_3040_SLOPE_REG 0x70
|
||||
#define DS2780_AE_2030_SLOPE_REG 0x71
|
||||
#define DS2780_AE_1020_SLOPE_REG 0x72
|
||||
#define DS2780_AE_0010_SLOPE_REG 0x73
|
||||
#define DS2780_SE_3040_SLOPE_REG 0x74
|
||||
#define DS2780_SE_2030_SLOPE_REG 0x75
|
||||
#define DS2780_SE_1020_SLOPE_REG 0x76
|
||||
#define DS2780_SE_0010_SLOPE_REG 0x77
|
||||
#define DS2780_RSGAIN_MSB_REG 0x78
|
||||
#define DS2780_RSGAIN_LSB_REG 0x79
|
||||
#define DS2780_RSTC_REG 0x7A
|
||||
#define DS2780_FRSGAIN_MSB_REG 0x7B
|
||||
#define DS2780_FRSGAIN_LSB_REG 0x7C
|
||||
#define DS2780_EEPROM_BLOCK1_END 0x7C
|
||||
/* Register 0x7D - 0xFF Reserved */
|
||||
|
||||
/* Number of valid register addresses */
|
||||
#define DS2780_DATA_SIZE 0x80
|
||||
|
||||
/* Status register bits */
|
||||
#define DS2780_STATUS_REG_CHGTF (1 << 7)
|
||||
#define DS2780_STATUS_REG_AEF (1 << 6)
|
||||
#define DS2780_STATUS_REG_SEF (1 << 5)
|
||||
#define DS2780_STATUS_REG_LEARNF (1 << 4)
|
||||
/* Bit 3 Reserved */
|
||||
#define DS2780_STATUS_REG_UVF (1 << 2)
|
||||
#define DS2780_STATUS_REG_PORF (1 << 1)
|
||||
/* Bit 0 Reserved */
|
||||
|
||||
/* Control register bits */
|
||||
/* Bit 7 Reserved */
|
||||
#define DS2780_CONTROL_REG_UVEN (1 << 6)
|
||||
#define DS2780_CONTROL_REG_PMOD (1 << 5)
|
||||
#define DS2780_CONTROL_REG_RNAOP (1 << 4)
|
||||
/* Bit 0 - 3 Reserved */
|
||||
|
||||
/* Special feature register bits */
|
||||
/* Bit 1 - 7 Reserved */
|
||||
#define DS2780_SFR_REG_PIOSC (1 << 0)
|
||||
|
||||
/* EEPROM register bits */
|
||||
#define DS2780_EEPROM_REG_EEC (1 << 7)
|
||||
#define DS2780_EEPROM_REG_LOCK (1 << 6)
|
||||
/* Bit 2 - 6 Reserved */
|
||||
#define DS2780_EEPROM_REG_BL1 (1 << 1)
|
||||
#define DS2780_EEPROM_REG_BL0 (1 << 0)
|
||||
|
||||
extern int w1_ds2780_io(struct device *dev, char *buf, int addr, size_t count,
|
||||
int io);
|
||||
extern int w1_ds2780_eeprom_cmd(struct device *dev, int addr, int cmd);
|
||||
|
||||
#endif /* !_W1_DS2780_H */
|
189
drivers/w1/slaves/w1_ds2781.c
Normal file
189
drivers/w1/slaves/w1_ds2781.c
Normal file
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* 1-Wire implementation for the ds2781 chip
|
||||
*
|
||||
* Author: Renata Sayakhova <renata@oktetlabs.ru>
|
||||
*
|
||||
* Based on w1-ds2780 driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/idr.h>
|
||||
|
||||
#include "../w1.h"
|
||||
#include "../w1_int.h"
|
||||
#include "../w1_family.h"
|
||||
#include "w1_ds2781.h"
|
||||
|
||||
static int w1_ds2781_do_io(struct device *dev, char *buf, int addr,
|
||||
size_t count, int io)
|
||||
{
|
||||
struct w1_slave *sl = container_of(dev, struct w1_slave, dev);
|
||||
|
||||
if (addr > DS2781_DATA_SIZE || addr < 0)
|
||||
return 0;
|
||||
|
||||
count = min_t(int, count, DS2781_DATA_SIZE - addr);
|
||||
|
||||
if (w1_reset_select_slave(sl) == 0) {
|
||||
if (io) {
|
||||
w1_write_8(sl->master, W1_DS2781_WRITE_DATA);
|
||||
w1_write_8(sl->master, addr);
|
||||
w1_write_block(sl->master, buf, count);
|
||||
} else {
|
||||
w1_write_8(sl->master, W1_DS2781_READ_DATA);
|
||||
w1_write_8(sl->master, addr);
|
||||
count = w1_read_block(sl->master, buf, count);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int w1_ds2781_io(struct device *dev, char *buf, int addr, size_t count,
|
||||
int io)
|
||||
{
|
||||
struct w1_slave *sl = container_of(dev, struct w1_slave, dev);
|
||||
int ret;
|
||||
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
|
||||
ret = w1_ds2781_do_io(dev, buf, addr, count, io);
|
||||
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(w1_ds2781_io);
|
||||
|
||||
int w1_ds2781_eeprom_cmd(struct device *dev, int addr, int cmd)
|
||||
{
|
||||
struct w1_slave *sl = container_of(dev, struct w1_slave, dev);
|
||||
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&sl->master->bus_mutex);
|
||||
|
||||
if (w1_reset_select_slave(sl) == 0) {
|
||||
w1_write_8(sl->master, cmd);
|
||||
w1_write_8(sl->master, addr);
|
||||
}
|
||||
|
||||
mutex_unlock(&sl->master->bus_mutex);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(w1_ds2781_eeprom_cmd);
|
||||
|
||||
static ssize_t w1_slave_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
return w1_ds2781_io(dev, buf, off, count, 0);
|
||||
}
|
||||
|
||||
static BIN_ATTR_RO(w1_slave, DS2781_DATA_SIZE);
|
||||
|
||||
static struct bin_attribute *w1_ds2781_bin_attrs[] = {
|
||||
&bin_attr_w1_slave,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group w1_ds2781_group = {
|
||||
.bin_attrs = w1_ds2781_bin_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *w1_ds2781_groups[] = {
|
||||
&w1_ds2781_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static DEFINE_IDA(bat_ida);
|
||||
|
||||
static int w1_ds2781_add_slave(struct w1_slave *sl)
|
||||
{
|
||||
int ret;
|
||||
int id;
|
||||
struct platform_device *pdev;
|
||||
|
||||
id = ida_simple_get(&bat_ida, 0, 0, GFP_KERNEL);
|
||||
if (id < 0) {
|
||||
ret = id;
|
||||
goto noid;
|
||||
}
|
||||
|
||||
pdev = platform_device_alloc("ds2781-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;
|
||||
|
||||
dev_set_drvdata(&sl->dev, pdev);
|
||||
|
||||
return 0;
|
||||
|
||||
pdev_add_failed:
|
||||
platform_device_put(pdev);
|
||||
pdev_alloc_failed:
|
||||
ida_simple_remove(&bat_ida, id);
|
||||
noid:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void w1_ds2781_remove_slave(struct w1_slave *sl)
|
||||
{
|
||||
struct platform_device *pdev = dev_get_drvdata(&sl->dev);
|
||||
int id = pdev->id;
|
||||
|
||||
platform_device_unregister(pdev);
|
||||
ida_simple_remove(&bat_ida, id);
|
||||
}
|
||||
|
||||
static struct w1_family_ops w1_ds2781_fops = {
|
||||
.add_slave = w1_ds2781_add_slave,
|
||||
.remove_slave = w1_ds2781_remove_slave,
|
||||
.groups = w1_ds2781_groups,
|
||||
};
|
||||
|
||||
static struct w1_family w1_ds2781_family = {
|
||||
.fid = W1_FAMILY_DS2781,
|
||||
.fops = &w1_ds2781_fops,
|
||||
};
|
||||
|
||||
static int __init w1_ds2781_init(void)
|
||||
{
|
||||
ida_init(&bat_ida);
|
||||
return w1_register_family(&w1_ds2781_family);
|
||||
}
|
||||
|
||||
static void __exit w1_ds2781_exit(void)
|
||||
{
|
||||
w1_unregister_family(&w1_ds2781_family);
|
||||
ida_destroy(&bat_ida);
|
||||
}
|
||||
|
||||
module_init(w1_ds2781_init);
|
||||
module_exit(w1_ds2781_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Renata Sayakhova <renata@oktetlabs.ru>");
|
||||
MODULE_DESCRIPTION("1-wire Driver for Maxim/Dallas DS2781 Stand-Alone Fuel Gauge IC");
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2781));
|
134
drivers/w1/slaves/w1_ds2781.h
Normal file
134
drivers/w1/slaves/w1_ds2781.h
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 1-Wire implementation for the ds2780 chip
|
||||
*
|
||||
* Author: Renata Sayakhova <renata@oktetlabs.ru>
|
||||
*
|
||||
* Based on w1-ds2760 driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _W1_DS2781_H
|
||||
#define _W1_DS2781_H
|
||||
|
||||
/* Function commands */
|
||||
#define W1_DS2781_READ_DATA 0x69
|
||||
#define W1_DS2781_WRITE_DATA 0x6C
|
||||
#define W1_DS2781_COPY_DATA 0x48
|
||||
#define W1_DS2781_RECALL_DATA 0xB8
|
||||
#define W1_DS2781_LOCK 0x6A
|
||||
|
||||
/* Register map */
|
||||
/* Register 0x00 Reserved */
|
||||
#define DS2781_STATUS 0x01
|
||||
#define DS2781_RAAC_MSB 0x02
|
||||
#define DS2781_RAAC_LSB 0x03
|
||||
#define DS2781_RSAC_MSB 0x04
|
||||
#define DS2781_RSAC_LSB 0x05
|
||||
#define DS2781_RARC 0x06
|
||||
#define DS2781_RSRC 0x07
|
||||
#define DS2781_IAVG_MSB 0x08
|
||||
#define DS2781_IAVG_LSB 0x09
|
||||
#define DS2781_TEMP_MSB 0x0A
|
||||
#define DS2781_TEMP_LSB 0x0B
|
||||
#define DS2781_VOLT_MSB 0x0C
|
||||
#define DS2781_VOLT_LSB 0x0D
|
||||
#define DS2781_CURRENT_MSB 0x0E
|
||||
#define DS2781_CURRENT_LSB 0x0F
|
||||
#define DS2781_ACR_MSB 0x10
|
||||
#define DS2781_ACR_LSB 0x11
|
||||
#define DS2781_ACRL_MSB 0x12
|
||||
#define DS2781_ACRL_LSB 0x13
|
||||
#define DS2781_AS 0x14
|
||||
#define DS2781_SFR 0x15
|
||||
#define DS2781_FULL_MSB 0x16
|
||||
#define DS2781_FULL_LSB 0x17
|
||||
#define DS2781_AE_MSB 0x18
|
||||
#define DS2781_AE_LSB 0x19
|
||||
#define DS2781_SE_MSB 0x1A
|
||||
#define DS2781_SE_LSB 0x1B
|
||||
/* Register 0x1C - 0x1E Reserved */
|
||||
#define DS2781_EEPROM 0x1F
|
||||
#define DS2781_EEPROM_BLOCK0_START 0x20
|
||||
/* Register 0x20 - 0x2F User EEPROM */
|
||||
#define DS2781_EEPROM_BLOCK0_END 0x2F
|
||||
/* Register 0x30 - 0x5F Reserved */
|
||||
#define DS2781_EEPROM_BLOCK1_START 0x60
|
||||
#define DS2781_CONTROL 0x60
|
||||
#define DS2781_AB 0x61
|
||||
#define DS2781_AC_MSB 0x62
|
||||
#define DS2781_AC_LSB 0x63
|
||||
#define DS2781_VCHG 0x64
|
||||
#define DS2781_IMIN 0x65
|
||||
#define DS2781_VAE 0x66
|
||||
#define DS2781_IAE 0x67
|
||||
#define DS2781_AE_40 0x68
|
||||
#define DS2781_RSNSP 0x69
|
||||
#define DS2781_FULL_40_MSB 0x6A
|
||||
#define DS2781_FULL_40_LSB 0x6B
|
||||
#define DS2781_FULL_4_SLOPE 0x6C
|
||||
#define DS2781_FULL_3_SLOPE 0x6D
|
||||
#define DS2781_FULL_2_SLOPE 0x6E
|
||||
#define DS2781_FULL_1_SLOPE 0x6F
|
||||
#define DS2781_AE_4_SLOPE 0x70
|
||||
#define DS2781_AE_3_SLOPE 0x71
|
||||
#define DS2781_AE_2_SLOPE 0x72
|
||||
#define DS2781_AE_1_SLOPE 0x73
|
||||
#define DS2781_SE_4_SLOPE 0x74
|
||||
#define DS2781_SE_3_SLOPE 0x75
|
||||
#define DS2781_SE_2_SLOPE 0x76
|
||||
#define DS2781_SE_1_SLOPE 0x77
|
||||
#define DS2781_RSGAIN_MSB 0x78
|
||||
#define DS2781_RSGAIN_LSB 0x79
|
||||
#define DS2781_RSTC 0x7A
|
||||
#define DS2781_COB 0x7B
|
||||
#define DS2781_TBP34 0x7C
|
||||
#define DS2781_TBP23 0x7D
|
||||
#define DS2781_TBP12 0x7E
|
||||
#define DS2781_EEPROM_BLOCK1_END 0x7F
|
||||
/* Register 0x7D - 0xFF Reserved */
|
||||
|
||||
#define DS2781_FSGAIN_MSB 0xB0
|
||||
#define DS2781_FSGAIN_LSB 0xB1
|
||||
|
||||
/* Number of valid register addresses */
|
||||
#define DS2781_DATA_SIZE 0xB2
|
||||
|
||||
/* Status register bits */
|
||||
#define DS2781_STATUS_CHGTF (1 << 7)
|
||||
#define DS2781_STATUS_AEF (1 << 6)
|
||||
#define DS2781_STATUS_SEF (1 << 5)
|
||||
#define DS2781_STATUS_LEARNF (1 << 4)
|
||||
/* Bit 3 Reserved */
|
||||
#define DS2781_STATUS_UVF (1 << 2)
|
||||
#define DS2781_STATUS_PORF (1 << 1)
|
||||
/* Bit 0 Reserved */
|
||||
|
||||
/* Control register bits */
|
||||
/* Bit 7 Reserved */
|
||||
#define DS2781_CONTROL_NBEN (1 << 7)
|
||||
#define DS2781_CONTROL_UVEN (1 << 6)
|
||||
#define DS2781_CONTROL_PMOD (1 << 5)
|
||||
#define DS2781_CONTROL_RNAOP (1 << 4)
|
||||
#define DS1781_CONTROL_UVTH (1 << 3)
|
||||
/* Bit 0 - 2 Reserved */
|
||||
|
||||
/* Special feature register bits */
|
||||
/* Bit 1 - 7 Reserved */
|
||||
#define DS2781_SFR_PIOSC (1 << 0)
|
||||
|
||||
/* EEPROM register bits */
|
||||
#define DS2781_EEPROM_EEC (1 << 7)
|
||||
#define DS2781_EEPROM_LOCK (1 << 6)
|
||||
/* Bit 2 - 6 Reserved */
|
||||
#define DS2781_EEPROM_BL1 (1 << 1)
|
||||
#define DS2781_EEPROM_BL0 (1 << 0)
|
||||
|
||||
extern int w1_ds2781_io(struct device *dev, char *buf, int addr, size_t count,
|
||||
int io);
|
||||
extern int w1_ds2781_eeprom_cmd(struct device *dev, int addr, int cmd);
|
||||
|
||||
#endif /* !_W1_DS2781_H */
|
442
drivers/w1/slaves/w1_ds28e04.c
Normal file
442
drivers/w1/slaves/w1_ds28e04.c
Normal file
|
@ -0,0 +1,442 @@
|
|||
/*
|
||||
* w1_ds28e04.c - w1 family 1C (DS28E04) driver
|
||||
*
|
||||
* Copyright (c) 2012 Markus Franke <franke.m@sebakmt.com>
|
||||
*
|
||||
* This source code is licensed under the GNU General Public License,
|
||||
* Version 2. See the file COPYING for more details.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/crc16.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#define CRC16_INIT 0
|
||||
#define CRC16_VALID 0xb001
|
||||
|
||||
#include "../w1.h"
|
||||
#include "../w1_int.h"
|
||||
#include "../w1_family.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Markus Franke <franke.m@sebakmt.com>, <franm@hrz.tu-chemnitz.de>");
|
||||
MODULE_DESCRIPTION("w1 family 1C driver for DS28E04, 4kb EEPROM and PIO");
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS28E04));
|
||||
|
||||
/* Allow the strong pullup to be disabled, but default to enabled.
|
||||
* If it was disabled a parasite powered device might not get the required
|
||||
* current to copy the data from the scratchpad to EEPROM. If it is enabled
|
||||
* parasite powered devices have a better chance of getting the current
|
||||
* required.
|
||||
*/
|
||||
static int w1_strong_pullup = 1;
|
||||
module_param_named(strong_pullup, w1_strong_pullup, int, 0);
|
||||
|
||||
/* enable/disable CRC checking on DS28E04-100 memory accesses */
|
||||
static char w1_enable_crccheck = 1;
|
||||
|
||||
#define W1_EEPROM_SIZE 512
|
||||
#define W1_PAGE_COUNT 16
|
||||
#define W1_PAGE_SIZE 32
|
||||
#define W1_PAGE_BITS 5
|
||||
#define W1_PAGE_MASK 0x1F
|
||||
|
||||
#define W1_F1C_READ_EEPROM 0xF0
|
||||
#define W1_F1C_WRITE_SCRATCH 0x0F
|
||||
#define W1_F1C_READ_SCRATCH 0xAA
|
||||
#define W1_F1C_COPY_SCRATCH 0x55
|
||||
#define W1_F1C_ACCESS_WRITE 0x5A
|
||||
|
||||
#define W1_1C_REG_LOGIC_STATE 0x220
|
||||
|
||||
struct w1_f1C_data {
|
||||
u8 memory[W1_EEPROM_SIZE];
|
||||
u32 validcrc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check the file size bounds and adjusts count as needed.
|
||||
* This would not be needed if the file size didn't reset to 0 after a write.
|
||||
*/
|
||||
static inline size_t w1_f1C_fix_count(loff_t off, size_t count, size_t size)
|
||||
{
|
||||
if (off > size)
|
||||
return 0;
|
||||
|
||||
if ((off + count) > size)
|
||||
return size - off;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int w1_f1C_refresh_block(struct w1_slave *sl, struct w1_f1C_data *data,
|
||||
int block)
|
||||
{
|
||||
u8 wrbuf[3];
|
||||
int off = block * W1_PAGE_SIZE;
|
||||
|
||||
if (data->validcrc & (1 << block))
|
||||
return 0;
|
||||
|
||||
if (w1_reset_select_slave(sl)) {
|
||||
data->validcrc = 0;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
wrbuf[0] = W1_F1C_READ_EEPROM;
|
||||
wrbuf[1] = off & 0xff;
|
||||
wrbuf[2] = off >> 8;
|
||||
w1_write_block(sl->master, wrbuf, 3);
|
||||
w1_read_block(sl->master, &data->memory[off], W1_PAGE_SIZE);
|
||||
|
||||
/* cache the block if the CRC is valid */
|
||||
if (crc16(CRC16_INIT, &data->memory[off], W1_PAGE_SIZE) == CRC16_VALID)
|
||||
data->validcrc |= (1 << block);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int w1_f1C_read(struct w1_slave *sl, int addr, int len, char *data)
|
||||
{
|
||||
u8 wrbuf[3];
|
||||
|
||||
/* read directly from the EEPROM */
|
||||
if (w1_reset_select_slave(sl))
|
||||
return -EIO;
|
||||
|
||||
wrbuf[0] = W1_F1C_READ_EEPROM;
|
||||
wrbuf[1] = addr & 0xff;
|
||||
wrbuf[2] = addr >> 8;
|
||||
|
||||
w1_write_block(sl->master, wrbuf, sizeof(wrbuf));
|
||||
return w1_read_block(sl->master, data, len);
|
||||
}
|
||||
|
||||
static ssize_t eeprom_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
struct w1_f1C_data *data = sl->family_data;
|
||||
int i, min_page, max_page;
|
||||
|
||||
count = w1_f1C_fix_count(off, count, W1_EEPROM_SIZE);
|
||||
if (count == 0)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&sl->master->mutex);
|
||||
|
||||
if (w1_enable_crccheck) {
|
||||
min_page = (off >> W1_PAGE_BITS);
|
||||
max_page = (off + count - 1) >> W1_PAGE_BITS;
|
||||
for (i = min_page; i <= max_page; i++) {
|
||||
if (w1_f1C_refresh_block(sl, data, i)) {
|
||||
count = -EIO;
|
||||
goto out_up;
|
||||
}
|
||||
}
|
||||
memcpy(buf, &data->memory[off], count);
|
||||
} else {
|
||||
count = w1_f1C_read(sl, off, count, buf);
|
||||
}
|
||||
|
||||
out_up:
|
||||
mutex_unlock(&sl->master->mutex);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to the scratchpad and reads it back for verification.
|
||||
* Then copies the scratchpad to EEPROM.
|
||||
* The data must be on one page.
|
||||
* The master must be locked.
|
||||
*
|
||||
* @param sl The slave structure
|
||||
* @param addr Address for the write
|
||||
* @param len length must be <= (W1_PAGE_SIZE - (addr & W1_PAGE_MASK))
|
||||
* @param data The data to write
|
||||
* @return 0=Success -1=failure
|
||||
*/
|
||||
static int w1_f1C_write(struct w1_slave *sl, int addr, int len, const u8 *data)
|
||||
{
|
||||
u8 wrbuf[4];
|
||||
u8 rdbuf[W1_PAGE_SIZE + 3];
|
||||
u8 es = (addr + len - 1) & 0x1f;
|
||||
unsigned int tm = 10;
|
||||
int i;
|
||||
struct w1_f1C_data *f1C = sl->family_data;
|
||||
|
||||
/* Write the data to the scratchpad */
|
||||
if (w1_reset_select_slave(sl))
|
||||
return -1;
|
||||
|
||||
wrbuf[0] = W1_F1C_WRITE_SCRATCH;
|
||||
wrbuf[1] = addr & 0xff;
|
||||
wrbuf[2] = addr >> 8;
|
||||
|
||||
w1_write_block(sl->master, wrbuf, 3);
|
||||
w1_write_block(sl->master, data, len);
|
||||
|
||||
/* Read the scratchpad and verify */
|
||||
if (w1_reset_select_slave(sl))
|
||||
return -1;
|
||||
|
||||
w1_write_8(sl->master, W1_F1C_READ_SCRATCH);
|
||||
w1_read_block(sl->master, rdbuf, len + 3);
|
||||
|
||||
/* Compare what was read against the data written */
|
||||
if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) ||
|
||||
(rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0))
|
||||
return -1;
|
||||
|
||||
/* Copy the scratchpad to EEPROM */
|
||||
if (w1_reset_select_slave(sl))
|
||||
return -1;
|
||||
|
||||
wrbuf[0] = W1_F1C_COPY_SCRATCH;
|
||||
wrbuf[3] = es;
|
||||
|
||||
for (i = 0; i < sizeof(wrbuf); ++i) {
|
||||
/* issue 10ms strong pullup (or delay) on the last byte
|
||||
for writing the data from the scratchpad to EEPROM */
|
||||
if (w1_strong_pullup && i == sizeof(wrbuf)-1)
|
||||
w1_next_pullup(sl->master, tm);
|
||||
|
||||
w1_write_8(sl->master, wrbuf[i]);
|
||||
}
|
||||
|
||||
if (!w1_strong_pullup)
|
||||
msleep(tm);
|
||||
|
||||
if (w1_enable_crccheck) {
|
||||
/* invalidate cached data */
|
||||
f1C->validcrc &= ~(1 << (addr >> W1_PAGE_BITS));
|
||||
}
|
||||
|
||||
/* Reset the bus to wake up the EEPROM (this may not be needed) */
|
||||
w1_reset_bus(sl->master);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t eeprom_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
int addr, len, idx;
|
||||
|
||||
count = w1_f1C_fix_count(off, count, W1_EEPROM_SIZE);
|
||||
if (count == 0)
|
||||
return 0;
|
||||
|
||||
if (w1_enable_crccheck) {
|
||||
/* can only write full blocks in cached mode */
|
||||
if ((off & W1_PAGE_MASK) || (count & W1_PAGE_MASK)) {
|
||||
dev_err(&sl->dev, "invalid offset/count off=%d cnt=%zd\n",
|
||||
(int)off, count);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* make sure the block CRCs are valid */
|
||||
for (idx = 0; idx < count; idx += W1_PAGE_SIZE) {
|
||||
if (crc16(CRC16_INIT, &buf[idx], W1_PAGE_SIZE)
|
||||
!= CRC16_VALID) {
|
||||
dev_err(&sl->dev, "bad CRC at offset %d\n",
|
||||
(int)off);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_lock(&sl->master->mutex);
|
||||
|
||||
/* Can only write data to one page at a time */
|
||||
idx = 0;
|
||||
while (idx < count) {
|
||||
addr = off + idx;
|
||||
len = W1_PAGE_SIZE - (addr & W1_PAGE_MASK);
|
||||
if (len > (count - idx))
|
||||
len = count - idx;
|
||||
|
||||
if (w1_f1C_write(sl, addr, len, &buf[idx]) < 0) {
|
||||
count = -EIO;
|
||||
goto out_up;
|
||||
}
|
||||
idx += len;
|
||||
}
|
||||
|
||||
out_up:
|
||||
mutex_unlock(&sl->master->mutex);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static BIN_ATTR_RW(eeprom, W1_EEPROM_SIZE);
|
||||
|
||||
static ssize_t pio_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf, loff_t off,
|
||||
size_t count)
|
||||
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
int ret;
|
||||
|
||||
/* check arguments */
|
||||
if (off != 0 || count != 1 || buf == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&sl->master->mutex);
|
||||
ret = w1_f1C_read(sl, W1_1C_REG_LOGIC_STATE, count, buf);
|
||||
mutex_unlock(&sl->master->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t pio_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf, loff_t off,
|
||||
size_t count)
|
||||
|
||||
{
|
||||
struct w1_slave *sl = kobj_to_w1_slave(kobj);
|
||||
u8 wrbuf[3];
|
||||
u8 ack;
|
||||
|
||||
/* check arguments */
|
||||
if (off != 0 || count != 1 || buf == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&sl->master->mutex);
|
||||
|
||||
/* Write the PIO data */
|
||||
if (w1_reset_select_slave(sl)) {
|
||||
mutex_unlock(&sl->master->mutex);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* set bit 7..2 to value '1' */
|
||||
*buf = *buf | 0xFC;
|
||||
|
||||
wrbuf[0] = W1_F1C_ACCESS_WRITE;
|
||||
wrbuf[1] = *buf;
|
||||
wrbuf[2] = ~(*buf);
|
||||
w1_write_block(sl->master, wrbuf, 3);
|
||||
|
||||
w1_read_block(sl->master, &ack, sizeof(ack));
|
||||
|
||||
mutex_unlock(&sl->master->mutex);
|
||||
|
||||
/* check for acknowledgement */
|
||||
if (ack != 0xAA)
|
||||
return -EIO;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static BIN_ATTR_RW(pio, 1);
|
||||
|
||||
static ssize_t crccheck_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
if (put_user(w1_enable_crccheck + 0x30, buf))
|
||||
return -EFAULT;
|
||||
|
||||
return sizeof(w1_enable_crccheck);
|
||||
}
|
||||
|
||||
static ssize_t crccheck_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
char val;
|
||||
|
||||
if (count != 1 || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
if (get_user(val, buf))
|
||||
return -EFAULT;
|
||||
|
||||
/* convert to decimal */
|
||||
val = val - 0x30;
|
||||
if (val != 0 && val != 1)
|
||||
return -EINVAL;
|
||||
|
||||
/* set the new value */
|
||||
w1_enable_crccheck = val;
|
||||
|
||||
return sizeof(w1_enable_crccheck);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(crccheck);
|
||||
|
||||
static struct attribute *w1_f1C_attrs[] = {
|
||||
&dev_attr_crccheck.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct bin_attribute *w1_f1C_bin_attrs[] = {
|
||||
&bin_attr_eeprom,
|
||||
&bin_attr_pio,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group w1_f1C_group = {
|
||||
.attrs = w1_f1C_attrs,
|
||||
.bin_attrs = w1_f1C_bin_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *w1_f1C_groups[] = {
|
||||
&w1_f1C_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int w1_f1C_add_slave(struct w1_slave *sl)
|
||||
{
|
||||
struct w1_f1C_data *data = NULL;
|
||||
|
||||
if (w1_enable_crccheck) {
|
||||
data = kzalloc(sizeof(struct w1_f1C_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
sl->family_data = data;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void w1_f1C_remove_slave(struct w1_slave *sl)
|
||||
{
|
||||
kfree(sl->family_data);
|
||||
sl->family_data = NULL;
|
||||
}
|
||||
|
||||
static struct w1_family_ops w1_f1C_fops = {
|
||||
.add_slave = w1_f1C_add_slave,
|
||||
.remove_slave = w1_f1C_remove_slave,
|
||||
.groups = w1_f1C_groups,
|
||||
};
|
||||
|
||||
static struct w1_family w1_family_1C = {
|
||||
.fid = W1_FAMILY_DS28E04,
|
||||
.fops = &w1_f1C_fops,
|
||||
};
|
||||
|
||||
static int __init w1_f1C_init(void)
|
||||
{
|
||||
return w1_register_family(&w1_family_1C);
|
||||
}
|
||||
|
||||
static void __exit w1_f1C_fini(void)
|
||||
{
|
||||
w1_unregister_family(&w1_family_1C);
|
||||
}
|
||||
|
||||
module_init(w1_f1C_init);
|
||||
module_exit(w1_f1C_fini);
|
72
drivers/w1/slaves/w1_smem.c
Normal file
72
drivers/w1/slaves/w1_smem.c
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* w1_smem.c
|
||||
*
|
||||
* Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the smems of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <asm/types.h>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "../w1.h"
|
||||
#include "../w1_int.h"
|
||||
#include "../w1_family.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>");
|
||||
MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol, 64bit memory family.");
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_SMEM_01));
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_SMEM_81));
|
||||
|
||||
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);
|
310
drivers/w1/slaves/w1_therm.c
Normal file
310
drivers/w1/slaves/w1_therm.c
Normal file
|
@ -0,0 +1,310 @@
|
|||
/*
|
||||
* w1_therm.c
|
||||
*
|
||||
* Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the therms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <asm/types.h>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "../w1.h"
|
||||
#include "../w1_int.h"
|
||||
#include "../w1_family.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>");
|
||||
MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol, temperature family.");
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS18S20));
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS1822));
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS18B20));
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS1825));
|
||||
MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS28EA00));
|
||||
|
||||
/* Allow the strong pullup to be disabled, but default to enabled.
|
||||
* If it was disabled a parasite powered device might not get the require
|
||||
* current to do a temperature conversion. If it is enabled parasite powered
|
||||
* devices have a better chance of getting the current required.
|
||||
* In case the parasite power-detection is not working (seems to be the case
|
||||
* for some DS18S20) the strong pullup can also be forced, regardless of the
|
||||
* power state of the devices.
|
||||
*
|
||||
* Summary of options:
|
||||
* - strong_pullup = 0 Disable strong pullup completely
|
||||
* - strong_pullup = 1 Enable automatic strong pullup detection
|
||||
* - strong_pullup = 2 Force strong pullup
|
||||
*/
|
||||
static int w1_strong_pullup = 1;
|
||||
module_param_named(strong_pullup, w1_strong_pullup, int, 0);
|
||||
|
||||
static int w1_therm_add_slave(struct w1_slave *sl)
|
||||
{
|
||||
sl->family_data = kzalloc(9, GFP_KERNEL);
|
||||
if (!sl->family_data)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void w1_therm_remove_slave(struct w1_slave *sl)
|
||||
{
|
||||
kfree(sl->family_data);
|
||||
sl->family_data = NULL;
|
||||
}
|
||||
|
||||
static ssize_t w1_slave_show(struct device *device,
|
||||
struct device_attribute *attr, char *buf);
|
||||
|
||||
static DEVICE_ATTR_RO(w1_slave);
|
||||
|
||||
static struct attribute *w1_therm_attrs[] = {
|
||||
&dev_attr_w1_slave.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(w1_therm);
|
||||
|
||||
static struct w1_family_ops w1_therm_fops = {
|
||||
.add_slave = w1_therm_add_slave,
|
||||
.remove_slave = w1_therm_remove_slave,
|
||||
.groups = w1_therm_groups,
|
||||
};
|
||||
|
||||
static struct w1_family w1_therm_family_DS18S20 = {
|
||||
.fid = W1_THERM_DS18S20,
|
||||
.fops = &w1_therm_fops,
|
||||
};
|
||||
|
||||
static struct w1_family w1_therm_family_DS18B20 = {
|
||||
.fid = W1_THERM_DS18B20,
|
||||
.fops = &w1_therm_fops,
|
||||
};
|
||||
|
||||
static struct w1_family w1_therm_family_DS1822 = {
|
||||
.fid = W1_THERM_DS1822,
|
||||
.fops = &w1_therm_fops,
|
||||
};
|
||||
|
||||
static struct w1_family w1_therm_family_DS28EA00 = {
|
||||
.fid = W1_THERM_DS28EA00,
|
||||
.fops = &w1_therm_fops,
|
||||
};
|
||||
|
||||
static struct w1_family w1_therm_family_DS1825 = {
|
||||
.fid = W1_THERM_DS1825,
|
||||
.fops = &w1_therm_fops,
|
||||
};
|
||||
|
||||
struct w1_therm_family_converter
|
||||
{
|
||||
u8 broken;
|
||||
u16 reserved;
|
||||
struct w1_family *f;
|
||||
int (*convert)(u8 rom[9]);
|
||||
};
|
||||
|
||||
/* 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
|
||||
},
|
||||
{
|
||||
.f = &w1_therm_family_DS28EA00,
|
||||
.convert = w1_DS18B20_convert_temp
|
||||
},
|
||||
{
|
||||
.f = &w1_therm_family_DS1825,
|
||||
.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 ssize_t w1_slave_show(struct device *device,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct w1_slave *sl = dev_to_w1_slave(device);
|
||||
struct w1_master *dev = sl->master;
|
||||
u8 rom[9], crc, verdict, external_power;
|
||||
int i, max_trying = 10;
|
||||
ssize_t c = PAGE_SIZE;
|
||||
|
||||
i = mutex_lock_interruptible(&dev->bus_mutex);
|
||||
if (i != 0)
|
||||
return i;
|
||||
|
||||
memset(rom, 0, sizeof(rom));
|
||||
|
||||
while (max_trying--) {
|
||||
|
||||
verdict = 0;
|
||||
crc = 0;
|
||||
|
||||
if (!w1_reset_select_slave(sl)) {
|
||||
int count = 0;
|
||||
unsigned int tm = 750;
|
||||
unsigned long sleep_rem;
|
||||
|
||||
w1_write_8(dev, W1_READ_PSUPPLY);
|
||||
external_power = w1_read_8(dev);
|
||||
|
||||
if (w1_reset_select_slave(sl))
|
||||
continue;
|
||||
|
||||
/* 750ms strong pullup (or delay) after the convert */
|
||||
if (w1_strong_pullup == 2 ||
|
||||
(!external_power && w1_strong_pullup))
|
||||
w1_next_pullup(dev, tm);
|
||||
|
||||
w1_write_8(dev, W1_CONVERT_TEMP);
|
||||
|
||||
if (external_power) {
|
||||
mutex_unlock(&dev->bus_mutex);
|
||||
|
||||
sleep_rem = msleep_interruptible(tm);
|
||||
if (sleep_rem != 0)
|
||||
return -EINTR;
|
||||
|
||||
i = mutex_lock_interruptible(&dev->bus_mutex);
|
||||
if (i != 0)
|
||||
return i;
|
||||
} else if (!w1_strong_pullup) {
|
||||
sleep_rem = msleep_interruptible(tm);
|
||||
if (sleep_rem != 0) {
|
||||
mutex_unlock(&dev->bus_mutex);
|
||||
return -EINTR;
|
||||
}
|
||||
}
|
||||
|
||||
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 (verdict)
|
||||
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->family_data, rom, sizeof(rom));
|
||||
else
|
||||
dev_warn(device, "Read failed CRC check\n");
|
||||
|
||||
for (i = 0; i < 9; ++i)
|
||||
c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ",
|
||||
((u8 *)sl->family_data)[i]);
|
||||
|
||||
c -= snprintf(buf + PAGE_SIZE - c, c, "t=%d\n",
|
||||
w1_convert_temp(rom, sl->family->fid));
|
||||
mutex_unlock(&dev->bus_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);
|
1223
drivers/w1/w1.c
Normal file
1223
drivers/w1/w1.c
Normal file
File diff suppressed because it is too large
Load diff
341
drivers/w1/w1.h
Normal file
341
drivers/w1/w1.h
Normal file
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
* w1.h
|
||||
*
|
||||
* Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef __W1_H
|
||||
#define __W1_H
|
||||
|
||||
/**
|
||||
* struct w1_reg_num - broken out slave device id
|
||||
*
|
||||
* @family: identifies the type of device
|
||||
* @id: along with family is the unique device id
|
||||
* @crc: checksum of the other bytes
|
||||
*/
|
||||
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
|
||||
#define W1_SLAVE_DETACH 1
|
||||
|
||||
/**
|
||||
* struct w1_slave - holds a single slave device on the bus
|
||||
*
|
||||
* @owner: Points to the one wire "wire" kernel module.
|
||||
* @name: Device id is ascii.
|
||||
* @w1_slave_entry: data for the linked list
|
||||
* @reg_num: the slave id in binary
|
||||
* @refcnt: reference count, delete when 0
|
||||
* @flags: bit flags for W1_SLAVE_ACTIVE W1_SLAVE_DETACH
|
||||
* @ttl: decrement per search this slave isn't found, deatch at 0
|
||||
* @master: bus which this slave is on
|
||||
* @family: module for device family type
|
||||
* @family_data: pointer for use by the family module
|
||||
* @dev: kernel device identifier
|
||||
*
|
||||
*/
|
||||
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;
|
||||
int ttl;
|
||||
unsigned long flags;
|
||||
|
||||
struct w1_master *master;
|
||||
struct w1_family *family;
|
||||
void *family_data;
|
||||
struct device dev;
|
||||
};
|
||||
|
||||
typedef void (*w1_slave_found_callback)(struct w1_master *, u64);
|
||||
|
||||
|
||||
/**
|
||||
* struct w1_bus_master - operations available on a bus master
|
||||
*
|
||||
* @data: the first parameter in all the functions below
|
||||
*
|
||||
* @read_bit: Sample the line level @return the level read (0 or 1)
|
||||
*
|
||||
* @write_bit: Sets the line level
|
||||
*
|
||||
* @touch_bit: 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)
|
||||
*
|
||||
* @read_byte: Reads a bytes. Same as 8 touch_bit(1) calls.
|
||||
* @return the byte read
|
||||
*
|
||||
* @write_byte: Writes a byte. Same as 8 touch_bit(x) calls.
|
||||
*
|
||||
* @read_block: Same as a series of read_byte() calls
|
||||
* @return the number of bytes read
|
||||
*
|
||||
* @write_block: Same as a series of write_byte() calls
|
||||
*
|
||||
* @triplet: Combines two reads and a smart write for ROM searches
|
||||
* @return bit0=Id bit1=comp_id bit2=dir_taken
|
||||
*
|
||||
* @reset_bus: long write-0 with a read for the presence pulse detection
|
||||
* @return -1=Error, 0=Device present, 1=No device present
|
||||
*
|
||||
* @set_pullup: Put out a strong pull-up pulse of the specified duration.
|
||||
* @return -1=Error, 0=completed
|
||||
*
|
||||
* @search: Really nice hardware can handles the different types of ROM search
|
||||
* w1_master* is passed to the slave found callback.
|
||||
* u8 is search_type, W1_SEARCH or W1_ALARM_SEARCH
|
||||
*
|
||||
* 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
|
||||
{
|
||||
void *data;
|
||||
|
||||
u8 (*read_bit)(void *);
|
||||
|
||||
void (*write_bit)(void *, u8);
|
||||
|
||||
u8 (*touch_bit)(void *, u8);
|
||||
|
||||
u8 (*read_byte)(void *);
|
||||
|
||||
void (*write_byte)(void *, u8);
|
||||
|
||||
u8 (*read_block)(void *, u8 *, int);
|
||||
|
||||
void (*write_block)(void *, const u8 *, int);
|
||||
|
||||
u8 (*triplet)(void *, u8);
|
||||
|
||||
u8 (*reset_bus)(void *);
|
||||
|
||||
u8 (*set_pullup)(void *, int);
|
||||
|
||||
void (*search)(void *, struct w1_master *,
|
||||
u8, w1_slave_found_callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* enum w1_master_flags - bitfields used in w1_master.flags
|
||||
* @W1_ABORT_SEARCH: abort searching early on shutdown
|
||||
* @W1_WARN_MAX_COUNT: limit warning when the maximum count is reached
|
||||
*/
|
||||
enum w1_master_flags {
|
||||
W1_ABORT_SEARCH = 0,
|
||||
W1_WARN_MAX_COUNT = 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct w1_master - one per bus master
|
||||
* @w1_master_entry: master linked list
|
||||
* @owner: module owner
|
||||
* @name: dynamically allocate bus name
|
||||
* @list_mutex: protect slist and async_list
|
||||
* @slist: linked list of slaves
|
||||
* @async_list: linked list of netlink commands to execute
|
||||
* @max_slave_count: maximum number of slaves to search for at a time
|
||||
* @slave_count: current number of slaves known
|
||||
* @attempts: number of searches ran
|
||||
* @slave_ttl: number of searches before a slave is timed out
|
||||
* @initialized: prevent init/removal race conditions
|
||||
* @id: w1 bus number
|
||||
* @search_count: number of automatic searches to run, -1 unlimited
|
||||
* @search_id: allows continuing a search
|
||||
* @refcnt: reference count
|
||||
* @priv: private data storage
|
||||
* @enable_pullup: allows a strong pullup
|
||||
* @pullup_duration: time for the next strong pullup
|
||||
* @flags: one of w1_master_flags
|
||||
* @thread: thread for bus search and netlink commands
|
||||
* @mutex: protect most of w1_master
|
||||
* @bus_mutex: pretect concurrent bus access
|
||||
* @driver: sysfs driver
|
||||
* @dev: sysfs device
|
||||
* @bus_master: io operations available
|
||||
* @seq: sequence number used for netlink broadcasts
|
||||
*/
|
||||
struct w1_master
|
||||
{
|
||||
struct list_head w1_master_entry;
|
||||
struct module *owner;
|
||||
unsigned char name[W1_MAXNAMELEN];
|
||||
/* list_mutex protects just slist and async_list so slaves can be
|
||||
* searched for and async commands added while the master has
|
||||
* w1_master.mutex locked and is operating on the bus.
|
||||
* lock order w1_mlock, w1_master.mutex, w1_master.list_mutex
|
||||
*/
|
||||
struct mutex list_mutex;
|
||||
struct list_head slist;
|
||||
struct list_head async_list;
|
||||
int max_slave_count, slave_count;
|
||||
unsigned long attempts;
|
||||
int slave_ttl;
|
||||
int initialized;
|
||||
u32 id;
|
||||
int search_count;
|
||||
/* id to start searching on, to continue a search or 0 to restart */
|
||||
u64 search_id;
|
||||
|
||||
atomic_t refcnt;
|
||||
|
||||
void *priv;
|
||||
|
||||
/** 5V strong pullup enabled flag, 1 enabled, zero disabled. */
|
||||
int enable_pullup;
|
||||
/** 5V strong pullup duration in milliseconds, zero disabled. */
|
||||
int pullup_duration;
|
||||
|
||||
long flags;
|
||||
|
||||
struct task_struct *thread;
|
||||
struct mutex mutex;
|
||||
struct mutex bus_mutex;
|
||||
|
||||
struct device_driver *driver;
|
||||
struct device dev;
|
||||
|
||||
struct w1_bus_master *bus_master;
|
||||
|
||||
u32 seq;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct w1_async_cmd - execute callback from the w1_process kthread
|
||||
* @async_entry: link entry
|
||||
* @cb: callback function, must list_del and destroy this list before
|
||||
* returning
|
||||
*
|
||||
* When inserted into the w1_master async_list, w1_process will execute
|
||||
* the callback. Embed this into the structure with the command details.
|
||||
*/
|
||||
struct w1_async_cmd {
|
||||
struct list_head async_entry;
|
||||
void (*cb)(struct w1_master *dev, struct w1_async_cmd *async_cmd);
|
||||
};
|
||||
|
||||
int w1_create_master_attributes(struct w1_master *);
|
||||
void w1_destroy_master_attributes(struct w1_master *master);
|
||||
void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb);
|
||||
void w1_search_devices(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb);
|
||||
/* call w1_unref_slave to release the reference counts w1_search_slave added */
|
||||
struct w1_slave *w1_search_slave(struct w1_reg_num *id);
|
||||
/* decrements the reference on sl->master and sl, and cleans up if zero
|
||||
* returns the reference count after it has been decremented */
|
||||
int w1_unref_slave(struct w1_slave *sl);
|
||||
void w1_slave_found(struct w1_master *dev, u64 rn);
|
||||
void w1_search_process_cb(struct w1_master *dev, u8 search_type,
|
||||
w1_slave_found_callback cb);
|
||||
struct w1_slave *w1_slave_search_device(struct w1_master *dev,
|
||||
struct w1_reg_num *rn);
|
||||
struct w1_master *w1_search_master_id(u32 id);
|
||||
|
||||
/* Disconnect and reconnect devices in the given family. Used for finding
|
||||
* unclaimed devices after a family has been registered or releasing devices
|
||||
* after a family has been unregistered. Set attach to 1 when a new family
|
||||
* has just been registered, to 0 when it has been unregistered.
|
||||
*/
|
||||
void w1_reconnect_slaves(struct w1_family *f, int attach);
|
||||
int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn);
|
||||
/* 0 success, otherwise EBUSY */
|
||||
int w1_slave_detach(struct w1_slave *sl);
|
||||
|
||||
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_callbacks(struct w1_master *dev);
|
||||
extern int w1_process(void *);
|
||||
|
||||
#endif /* __KERNEL__ */
|
||||
|
||||
#endif /* __W1_H */
|
148
drivers/w1/w1_family.c
Normal file
148
drivers/w1/w1_family.c
Normal file
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* w1_family.c
|
||||
*
|
||||
* Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/sched.h> /* schedule_timeout() */
|
||||
#include <linux/delay.h>
|
||||
#include <linux/export.h>
|
||||
|
||||
#include "w1_family.h"
|
||||
#include "w1.h"
|
||||
|
||||
DEFINE_SPINLOCK(w1_flock);
|
||||
static LIST_HEAD(w1_families);
|
||||
|
||||
/**
|
||||
* w1_register_family() - register a device family driver
|
||||
* @newf: family to register
|
||||
*/
|
||||
int w1_register_family(struct w1_family *newf)
|
||||
{
|
||||
struct list_head *ent, *n;
|
||||
struct w1_family *f;
|
||||
int ret = 0;
|
||||
|
||||
spin_lock(&w1_flock);
|
||||
list_for_each_safe(ent, n, &w1_families) {
|
||||
f = list_entry(ent, struct w1_family, family_entry);
|
||||
|
||||
if (f->fid == newf->fid) {
|
||||
ret = -EEXIST;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
atomic_set(&newf->refcnt, 0);
|
||||
list_add_tail(&newf->family_entry, &w1_families);
|
||||
}
|
||||
spin_unlock(&w1_flock);
|
||||
|
||||
/* check default devices against the new set of drivers */
|
||||
w1_reconnect_slaves(newf, 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_unregister_family() - unregister a device family driver
|
||||
* @fent: family to unregister
|
||||
*/
|
||||
void w1_unregister_family(struct w1_family *fent)
|
||||
{
|
||||
struct list_head *ent, *n;
|
||||
struct w1_family *f;
|
||||
|
||||
spin_lock(&w1_flock);
|
||||
list_for_each_safe(ent, n, &w1_families) {
|
||||
f = list_entry(ent, struct w1_family, family_entry);
|
||||
|
||||
if (f->fid == fent->fid) {
|
||||
list_del(&fent->family_entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
spin_unlock(&w1_flock);
|
||||
|
||||
/* deatch devices using this family code */
|
||||
w1_reconnect_slaves(fent, 0);
|
||||
|
||||
while (atomic_read(&fent->refcnt)) {
|
||||
pr_info("Waiting for family %u to become free: refcnt=%d.\n",
|
||||
fent->fid, atomic_read(&fent->refcnt));
|
||||
|
||||
if (msleep_interruptible(1000))
|
||||
flush_signals(current);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Should be called under w1_flock held.
|
||||
*/
|
||||
struct w1_family * w1_family_registered(u8 fid)
|
||||
{
|
||||
struct list_head *ent, *n;
|
||||
struct w1_family *f = NULL;
|
||||
int ret = 0;
|
||||
|
||||
list_for_each_safe(ent, n, &w1_families) {
|
||||
f = list_entry(ent, struct w1_family, family_entry);
|
||||
|
||||
if (f->fid == fid) {
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (ret) ? f : NULL;
|
||||
}
|
||||
|
||||
static void __w1_family_put(struct w1_family *f)
|
||||
{
|
||||
atomic_dec(&f->refcnt);
|
||||
}
|
||||
|
||||
void w1_family_put(struct w1_family *f)
|
||||
{
|
||||
spin_lock(&w1_flock);
|
||||
__w1_family_put(f);
|
||||
spin_unlock(&w1_flock);
|
||||
}
|
||||
|
||||
#if 0
|
||||
void w1_family_get(struct w1_family *f)
|
||||
{
|
||||
spin_lock(&w1_flock);
|
||||
__w1_family_get(f);
|
||||
spin_unlock(&w1_flock);
|
||||
}
|
||||
#endif /* 0 */
|
||||
|
||||
void __w1_family_get(struct w1_family *f)
|
||||
{
|
||||
smp_mb__before_atomic();
|
||||
atomic_inc(&f->refcnt);
|
||||
smp_mb__after_atomic();
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(w1_unregister_family);
|
||||
EXPORT_SYMBOL(w1_register_family);
|
90
drivers/w1/w1_family.h
Normal file
90
drivers/w1/w1_family.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* w1_family.h
|
||||
*
|
||||
* Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef __W1_FAMILY_H
|
||||
#define __W1_FAMILY_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/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_FAMILY_DS28E04 0x1C
|
||||
#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 W1_FAMILY_DS2413 0x3A
|
||||
#define W1_FAMILY_DS2406 0x12
|
||||
#define W1_THERM_DS1825 0x3B
|
||||
#define W1_FAMILY_DS2781 0x3D
|
||||
#define W1_THERM_DS28EA00 0x42
|
||||
|
||||
#define MAXNAMELEN 32
|
||||
|
||||
struct w1_slave;
|
||||
|
||||
/**
|
||||
* struct w1_family_ops - operations for a family type
|
||||
* @add_slave: add_slave
|
||||
* @remove_slave: remove_slave
|
||||
* @groups: sysfs group
|
||||
*/
|
||||
struct w1_family_ops
|
||||
{
|
||||
int (* add_slave)(struct w1_slave *);
|
||||
void (* remove_slave)(struct w1_slave *);
|
||||
const struct attribute_group **groups;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct w1_family - reference counted family structure.
|
||||
* @family_entry: family linked list
|
||||
* @fid: 8 bit family identifier
|
||||
* @fops: operations for this family
|
||||
* @refcnt: reference counter
|
||||
*/
|
||||
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 */
|
264
drivers/w1/w1_int.c
Normal file
264
drivers/w1/w1_int.c
Normal file
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* w1_int.c
|
||||
*
|
||||
* Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/moduleparam.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) {
|
||||
pr_err("Failed to allocate %zd bytes for new w1 device.\n",
|
||||
sizeof(struct w1_master));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
dev->bus_master = (struct w1_bus_master *)(dev + 1);
|
||||
|
||||
dev->owner = THIS_MODULE;
|
||||
dev->max_slave_count = slave_count;
|
||||
dev->slave_count = 0;
|
||||
dev->attempts = 0;
|
||||
dev->initialized = 0;
|
||||
dev->id = id;
|
||||
dev->slave_ttl = slave_ttl;
|
||||
dev->search_count = w1_search_count;
|
||||
dev->enable_pullup = w1_enable_pullup;
|
||||
|
||||
/* 1 for w1_process to decrement
|
||||
* 1 for __w1_remove_master_device to decrement
|
||||
*/
|
||||
atomic_set(&dev->refcnt, 2);
|
||||
|
||||
INIT_LIST_HEAD(&dev->slist);
|
||||
INIT_LIST_HEAD(&dev->async_list);
|
||||
mutex_init(&dev->mutex);
|
||||
mutex_init(&dev->bus_mutex);
|
||||
mutex_init(&dev->list_mutex);
|
||||
|
||||
memcpy(&dev->dev, device, sizeof(struct device));
|
||||
dev_set_name(&dev->dev, "w1_bus_master%u", dev->id);
|
||||
snprintf(dev->name, sizeof(dev->name), "w1_bus_master%u", dev->id);
|
||||
dev->dev.init_name = dev->name;
|
||||
|
||||
dev->driver = driver;
|
||||
|
||||
dev->seq = 1;
|
||||
|
||||
err = device_register(&dev->dev);
|
||||
if (err) {
|
||||
pr_err("Failed to register master device. err=%d\n", err);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_add_master_device() - registers a new master device
|
||||
* @master: master bus device to register
|
||||
*/
|
||||
int w1_add_master_device(struct w1_bus_master *master)
|
||||
{
|
||||
struct w1_master *dev, *entry;
|
||||
int retval = 0;
|
||||
struct w1_netlink_msg msg;
|
||||
int id, found;
|
||||
|
||||
/* validate minimum functionality */
|
||||
if (!(master->touch_bit && master->reset_bus) &&
|
||||
!(master->write_bit && master->read_bit) &&
|
||||
!(master->write_byte && master->read_byte && master->reset_bus)) {
|
||||
pr_err("w1_add_master_device: invalid function set\n");
|
||||
return(-EINVAL);
|
||||
}
|
||||
|
||||
/* Lock until the device is added (or not) to w1_masters. */
|
||||
mutex_lock(&w1_mlock);
|
||||
/* Search for the first available id (starting at 1). */
|
||||
id = 0;
|
||||
do {
|
||||
++id;
|
||||
found = 0;
|
||||
list_for_each_entry(entry, &w1_masters, w1_master_entry) {
|
||||
if (entry->id == id) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (found);
|
||||
|
||||
dev = w1_alloc_dev(id, w1_max_slave_count, w1_max_slave_ttl,
|
||||
&w1_master_driver, &w1_master_device);
|
||||
if (!dev) {
|
||||
mutex_unlock(&w1_mlock);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
retval = w1_create_master_attributes(dev);
|
||||
if (retval) {
|
||||
mutex_unlock(&w1_mlock);
|
||||
goto err_out_free_dev;
|
||||
}
|
||||
|
||||
memcpy(dev->bus_master, master, sizeof(struct w1_bus_master));
|
||||
|
||||
dev->initialized = 1;
|
||||
|
||||
dev->thread = kthread_run(&w1_process, dev, "%s", dev->name);
|
||||
if (IS_ERR(dev->thread)) {
|
||||
retval = PTR_ERR(dev->thread);
|
||||
dev_err(&dev->dev,
|
||||
"Failed to create new kernel thread. err=%d\n",
|
||||
retval);
|
||||
mutex_unlock(&w1_mlock);
|
||||
goto err_out_rm_attr;
|
||||
}
|
||||
|
||||
list_add(&dev->w1_master_entry, &w1_masters);
|
||||
mutex_unlock(&w1_mlock);
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.id.mst.id = dev->id;
|
||||
msg.type = W1_MASTER_ADD;
|
||||
w1_netlink_send(dev, &msg);
|
||||
|
||||
return 0;
|
||||
|
||||
#if 0 /* Thread cleanup code, not required currently. */
|
||||
err_out_kill_thread:
|
||||
set_bit(W1_ABORT_SEARCH, &dev->flags);
|
||||
kthread_stop(dev->thread);
|
||||
#endif
|
||||
err_out_rm_attr:
|
||||
w1_destroy_master_attributes(dev);
|
||||
err_out_free_dev:
|
||||
w1_free_dev(dev);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void __w1_remove_master_device(struct w1_master *dev)
|
||||
{
|
||||
struct w1_netlink_msg msg;
|
||||
struct w1_slave *sl, *sln;
|
||||
|
||||
mutex_lock(&w1_mlock);
|
||||
list_del(&dev->w1_master_entry);
|
||||
mutex_unlock(&w1_mlock);
|
||||
|
||||
set_bit(W1_ABORT_SEARCH, &dev->flags);
|
||||
kthread_stop(dev->thread);
|
||||
|
||||
mutex_lock(&dev->mutex);
|
||||
mutex_lock(&dev->list_mutex);
|
||||
list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) {
|
||||
mutex_unlock(&dev->list_mutex);
|
||||
w1_slave_detach(sl);
|
||||
mutex_lock(&dev->list_mutex);
|
||||
}
|
||||
w1_destroy_master_attributes(dev);
|
||||
mutex_unlock(&dev->list_mutex);
|
||||
mutex_unlock(&dev->mutex);
|
||||
atomic_dec(&dev->refcnt);
|
||||
|
||||
while (atomic_read(&dev->refcnt)) {
|
||||
dev_info(&dev->dev, "Waiting for %s to become free: refcnt=%d.\n",
|
||||
dev->name, atomic_read(&dev->refcnt));
|
||||
|
||||
if (msleep_interruptible(1000))
|
||||
flush_signals(current);
|
||||
mutex_lock(&dev->list_mutex);
|
||||
w1_process_callbacks(dev);
|
||||
mutex_unlock(&dev->list_mutex);
|
||||
}
|
||||
mutex_lock(&dev->list_mutex);
|
||||
w1_process_callbacks(dev);
|
||||
mutex_unlock(&dev->list_mutex);
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.id.mst.id = dev->id;
|
||||
msg.type = W1_MASTER_REMOVE;
|
||||
w1_netlink_send(dev, &msg);
|
||||
|
||||
w1_free_dev(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_remove_master_device() - unregister a master device
|
||||
* @bm: master bus device to remove
|
||||
*/
|
||||
void w1_remove_master_device(struct w1_bus_master *bm)
|
||||
{
|
||||
struct w1_master *dev, *found = NULL;
|
||||
|
||||
list_for_each_entry(dev, &w1_masters, w1_master_entry) {
|
||||
if (!dev->initialized)
|
||||
continue;
|
||||
|
||||
if (dev->bus_master->data == bm->data) {
|
||||
found = dev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
pr_err("Device doesn't exist.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
__w1_remove_master_device(found);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(w1_add_master_device);
|
||||
EXPORT_SYMBOL(w1_remove_master_device);
|
34
drivers/w1/w1_int.h
Normal file
34
drivers/w1/w1_int.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* w1_int.h
|
||||
*
|
||||
* Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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 */
|
464
drivers/w1/w1_io.c
Normal file
464
drivers/w1/w1_io.c
Normal file
|
@ -0,0 +1,464 @@
|
|||
/*
|
||||
* w1_io.c
|
||||
*
|
||||
* Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <asm/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 int w1_disable_irqs = 0;
|
||||
module_param_named(disable_irqs, w1_disable_irqs, int, 0);
|
||||
|
||||
static u8 w1_crc8_table[] = {
|
||||
0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,
|
||||
157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,
|
||||
35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98,
|
||||
190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,
|
||||
70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7,
|
||||
219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154,
|
||||
101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,
|
||||
248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,
|
||||
140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205,
|
||||
17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80,
|
||||
175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238,
|
||||
50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,
|
||||
202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,
|
||||
87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,
|
||||
233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,
|
||||
116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53
|
||||
};
|
||||
|
||||
static void w1_delay(unsigned long tm)
|
||||
{
|
||||
udelay(tm * w1_delay_parm);
|
||||
}
|
||||
|
||||
static void w1_write_bit(struct w1_master *dev, int bit);
|
||||
static u8 w1_read_bit(struct w1_master *dev);
|
||||
|
||||
/**
|
||||
* w1_touch_bit() - Generates a write-0 or write-1 cycle and samples the level.
|
||||
* @dev: the master device
|
||||
* @bit: 0 - write a 0, 1 - write a 0 read the level
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_write_bit() - Generates a write-0 or write-1 cycle.
|
||||
* @dev: the master device
|
||||
* @bit: bit to write
|
||||
*
|
||||
* Only call if dev->bus_master->touch_bit is NULL
|
||||
*/
|
||||
static void w1_write_bit(struct w1_master *dev, int bit)
|
||||
{
|
||||
unsigned long flags = 0;
|
||||
|
||||
if(w1_disable_irqs) local_irq_save(flags);
|
||||
|
||||
if (bit) {
|
||||
dev->bus_master->write_bit(dev->bus_master->data, 0);
|
||||
w1_delay(6);
|
||||
dev->bus_master->write_bit(dev->bus_master->data, 1);
|
||||
w1_delay(64);
|
||||
} else {
|
||||
dev->bus_master->write_bit(dev->bus_master->data, 0);
|
||||
w1_delay(60);
|
||||
dev->bus_master->write_bit(dev->bus_master->data, 1);
|
||||
w1_delay(10);
|
||||
}
|
||||
|
||||
if(w1_disable_irqs) local_irq_restore(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_pre_write() - pre-write operations
|
||||
* @dev: the master device
|
||||
*
|
||||
* Pre-write operation, currently only supporting strong pullups.
|
||||
* Program the hardware for a strong pullup, if one has been requested and
|
||||
* the hardware supports it.
|
||||
*/
|
||||
static void w1_pre_write(struct w1_master *dev)
|
||||
{
|
||||
if (dev->pullup_duration &&
|
||||
dev->enable_pullup && dev->bus_master->set_pullup) {
|
||||
dev->bus_master->set_pullup(dev->bus_master->data,
|
||||
dev->pullup_duration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_post_write() - post-write options
|
||||
* @dev: the master device
|
||||
*
|
||||
* Post-write operation, currently only supporting strong pullups.
|
||||
* If a strong pullup was requested, clear it if the hardware supports
|
||||
* them, or execute the delay otherwise, in either case clear the request.
|
||||
*/
|
||||
static void w1_post_write(struct w1_master *dev)
|
||||
{
|
||||
if (dev->pullup_duration) {
|
||||
if (dev->enable_pullup && dev->bus_master->set_pullup)
|
||||
dev->bus_master->set_pullup(dev->bus_master->data, 0);
|
||||
else
|
||||
msleep(dev->pullup_duration);
|
||||
dev->pullup_duration = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_write_8() - Writes 8 bits.
|
||||
* @dev: the master device
|
||||
* @byte: the byte to write
|
||||
*/
|
||||
void w1_write_8(struct w1_master *dev, u8 byte)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (dev->bus_master->write_byte) {
|
||||
w1_pre_write(dev);
|
||||
dev->bus_master->write_byte(dev->bus_master->data, byte);
|
||||
}
|
||||
else
|
||||
for (i = 0; i < 8; ++i) {
|
||||
if (i == 7)
|
||||
w1_pre_write(dev);
|
||||
w1_touch_bit(dev, (byte >> i) & 0x1);
|
||||
}
|
||||
w1_post_write(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(w1_write_8);
|
||||
|
||||
|
||||
/**
|
||||
* w1_read_bit() - Generates a write-1 cycle and samples the level.
|
||||
* @dev: the master device
|
||||
*
|
||||
* Only call if dev->bus_master->touch_bit is NULL
|
||||
*/
|
||||
static u8 w1_read_bit(struct w1_master *dev)
|
||||
{
|
||||
int result;
|
||||
unsigned long flags = 0;
|
||||
|
||||
/* sample timing is critical here */
|
||||
local_irq_save(flags);
|
||||
dev->bus_master->write_bit(dev->bus_master->data, 0);
|
||||
w1_delay(6);
|
||||
dev->bus_master->write_bit(dev->bus_master->data, 1);
|
||||
w1_delay(9);
|
||||
|
||||
result = dev->bus_master->read_bit(dev->bus_master->data);
|
||||
local_irq_restore(flags);
|
||||
|
||||
w1_delay(55);
|
||||
|
||||
return result & 0x1;
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_triplet() - * Does a triplet - used for searching ROM addresses.
|
||||
* @dev: the master device
|
||||
* @bdir: the bit to write if both id_bit and comp_bit are 0
|
||||
*
|
||||
* Return bits:
|
||||
* bit 0 = id_bit
|
||||
* bit 1 = comp_bit
|
||||
* bit 2 = dir_taken
|
||||
* If both bits 0 & 1 are set, the search should be restarted.
|
||||
*
|
||||
* Return: bit fields - see above
|
||||
*/
|
||||
u8 w1_triplet(struct w1_master *dev, int bdir)
|
||||
{
|
||||
if (dev->bus_master->triplet)
|
||||
return dev->bus_master->triplet(dev->bus_master->data, bdir);
|
||||
else {
|
||||
u8 id_bit = w1_touch_bit(dev, 1);
|
||||
u8 comp_bit = w1_touch_bit(dev, 1);
|
||||
u8 retval;
|
||||
|
||||
if (id_bit && comp_bit)
|
||||
return 0x03; /* error */
|
||||
|
||||
if (!id_bit && !comp_bit) {
|
||||
/* Both bits are valid, take the direction given */
|
||||
retval = bdir ? 0x04 : 0;
|
||||
} else {
|
||||
/* Only one bit is valid, take that direction */
|
||||
bdir = id_bit;
|
||||
retval = id_bit ? 0x05 : 0x02;
|
||||
}
|
||||
|
||||
if (dev->bus_master->touch_bit)
|
||||
w1_touch_bit(dev, bdir);
|
||||
else
|
||||
w1_write_bit(dev, bdir);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_read_8() - Reads 8 bits.
|
||||
* @dev: the master device
|
||||
*
|
||||
* Return: the byte read
|
||||
*/
|
||||
u8 w1_read_8(struct w1_master *dev)
|
||||
{
|
||||
int i;
|
||||
u8 res = 0;
|
||||
|
||||
if (dev->bus_master->read_byte)
|
||||
res = dev->bus_master->read_byte(dev->bus_master->data);
|
||||
else
|
||||
for (i = 0; i < 8; ++i)
|
||||
res |= (w1_touch_bit(dev,1) << i);
|
||||
|
||||
return res;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(w1_read_8);
|
||||
|
||||
/**
|
||||
* w1_write_block() - Writes a series of bytes.
|
||||
* @dev: the master device
|
||||
* @buf: pointer to the data to write
|
||||
* @len: the number of bytes to write
|
||||
*/
|
||||
void w1_write_block(struct w1_master *dev, const u8 *buf, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (dev->bus_master->write_block) {
|
||||
w1_pre_write(dev);
|
||||
dev->bus_master->write_block(dev->bus_master->data, buf, len);
|
||||
}
|
||||
else
|
||||
for (i = 0; i < len; ++i)
|
||||
w1_write_8(dev, buf[i]); /* calls w1_pre_write */
|
||||
w1_post_write(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(w1_write_block);
|
||||
|
||||
/**
|
||||
* w1_touch_block() - Touches a series of bytes.
|
||||
* @dev: the master device
|
||||
* @buf: pointer to the data to write
|
||||
* @len: the number of bytes to write
|
||||
*/
|
||||
void w1_touch_block(struct w1_master *dev, u8 *buf, int len)
|
||||
{
|
||||
int i, j;
|
||||
u8 tmp;
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
tmp = 0;
|
||||
for (j = 0; j < 8; ++j) {
|
||||
if (j == 7)
|
||||
w1_pre_write(dev);
|
||||
tmp |= w1_touch_bit(dev, (buf[i] >> j) & 0x1) << j;
|
||||
}
|
||||
|
||||
buf[i] = tmp;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(w1_touch_block);
|
||||
|
||||
/**
|
||||
* w1_read_block() - Reads a series of bytes.
|
||||
* @dev: the master device
|
||||
* @buf: pointer to the buffer to fill
|
||||
* @len: the number of bytes to read
|
||||
* Return: the number of bytes read
|
||||
*/
|
||||
u8 w1_read_block(struct w1_master *dev, u8 *buf, int len)
|
||||
{
|
||||
int i;
|
||||
u8 ret;
|
||||
|
||||
if (dev->bus_master->read_block)
|
||||
ret = dev->bus_master->read_block(dev->bus_master->data, buf, len);
|
||||
else {
|
||||
for (i = 0; i < len; ++i)
|
||||
buf[i] = w1_read_8(dev);
|
||||
ret = len;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(w1_read_block);
|
||||
|
||||
/**
|
||||
* w1_reset_bus() - Issues a reset bus sequence.
|
||||
* @dev: the master device
|
||||
* Return: 0=Device present, 1=No device present or error
|
||||
*/
|
||||
int w1_reset_bus(struct w1_master *dev)
|
||||
{
|
||||
int result;
|
||||
unsigned long flags = 0;
|
||||
|
||||
if(w1_disable_irqs) local_irq_save(flags);
|
||||
|
||||
if (dev->bus_master->reset_bus)
|
||||
result = dev->bus_master->reset_bus(dev->bus_master->data) & 0x1;
|
||||
else {
|
||||
dev->bus_master->write_bit(dev->bus_master->data, 0);
|
||||
/* minimum 480, max ? us
|
||||
* be nice and sleep, except 18b20 spec lists 960us maximum,
|
||||
* so until we can sleep with microsecond accuracy, spin.
|
||||
* Feel free to come up with some other way to give up the
|
||||
* cpu for such a short amount of time AND get it back in
|
||||
* the maximum amount of time.
|
||||
*/
|
||||
w1_delay(500);
|
||||
dev->bus_master->write_bit(dev->bus_master->data, 1);
|
||||
w1_delay(70);
|
||||
|
||||
result = dev->bus_master->read_bit(dev->bus_master->data) & 0x1;
|
||||
/* minmum 70 (above) + 430 = 500 us
|
||||
* There aren't any timing requirements between a reset and
|
||||
* the following transactions. Sleeping is safe here.
|
||||
*/
|
||||
/* w1_delay(430); min required time */
|
||||
msleep(1);
|
||||
}
|
||||
|
||||
if(w1_disable_irqs) local_irq_restore(flags);
|
||||
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(w1_reset_bus);
|
||||
|
||||
u8 w1_calc_crc8(u8 * data, int len)
|
||||
{
|
||||
u8 crc = 0;
|
||||
|
||||
while (len--)
|
||||
crc = w1_crc8_table[crc ^ *data++];
|
||||
|
||||
return crc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(w1_calc_crc8);
|
||||
|
||||
void w1_search_devices(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb)
|
||||
{
|
||||
dev->attempts++;
|
||||
if (dev->bus_master->search)
|
||||
dev->bus_master->search(dev->bus_master->data, dev,
|
||||
search_type, cb);
|
||||
else
|
||||
w1_search(dev, search_type, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_reset_select_slave() - reset and select a slave
|
||||
* @sl: the slave to select
|
||||
*
|
||||
* Resets the bus and then selects the slave by sending either a skip rom
|
||||
* or a rom match. A skip rom is issued if there is only one device
|
||||
* registered on the bus.
|
||||
* The w1 master lock must be held.
|
||||
*
|
||||
* Return: 0=success, anything else=error
|
||||
*/
|
||||
int w1_reset_select_slave(struct w1_slave *sl)
|
||||
{
|
||||
if (w1_reset_bus(sl->master))
|
||||
return -1;
|
||||
|
||||
if (sl->master->slave_count == 1)
|
||||
w1_write_8(sl->master, W1_SKIP_ROM);
|
||||
else {
|
||||
u8 match[9] = {W1_MATCH_ROM, };
|
||||
u64 rn = le64_to_cpu(*((u64*)&sl->reg_num));
|
||||
|
||||
memcpy(&match[1], &rn, 8);
|
||||
w1_write_block(sl->master, match, 9);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(w1_reset_select_slave);
|
||||
|
||||
/**
|
||||
* w1_reset_resume_command() - resume instead of another match ROM
|
||||
* @dev: the master device
|
||||
*
|
||||
* When the workflow with a slave amongst many requires several
|
||||
* successive commands a reset between each, this function is similar
|
||||
* to doing a reset then a match ROM for the last matched ROM. The
|
||||
* advantage being that the matched ROM step is skipped in favor of the
|
||||
* resume command. The slave must support the command of course.
|
||||
*
|
||||
* If the bus has only one slave, traditionnaly the match ROM is skipped
|
||||
* and a "SKIP ROM" is done for efficiency. On multi-slave busses, this
|
||||
* doesn't work of course, but the resume command is the next best thing.
|
||||
*
|
||||
* The w1 master lock must be held.
|
||||
*/
|
||||
int w1_reset_resume_command(struct w1_master *dev)
|
||||
{
|
||||
if (w1_reset_bus(dev))
|
||||
return -1;
|
||||
|
||||
/* 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);
|
||||
|
||||
/**
|
||||
* w1_next_pullup() - register for a strong pullup
|
||||
* @dev: the master device
|
||||
* @delay: time in milliseconds
|
||||
*
|
||||
* Put out a strong pull-up of the specified duration after the next write
|
||||
* operation. Not all hardware supports strong pullups. Hardware that
|
||||
* doesn't support strong pullups will sleep for the given time after the
|
||||
* write operation without a strong pullup. This is a one shot request for
|
||||
* the next write, specifying zero will clear a previous request.
|
||||
* The w1 master lock must be held.
|
||||
*
|
||||
* Return: 0=success, anything else=error
|
||||
*/
|
||||
void w1_next_pullup(struct w1_master *dev, int delay)
|
||||
{
|
||||
dev->pullup_duration = delay;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(w1_next_pullup);
|
38
drivers/w1/w1_log.h
Normal file
38
drivers/w1/w1_log.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* w1_log.h
|
||||
*
|
||||
* Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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))) { \
|
||||
pr_err("Assertion failed! %s,%s,%s,line=%d\n", \
|
||||
#expr, __FILE__, __func__, __LINE__); \
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __W1_LOG_H */
|
||||
|
758
drivers/w1/w1_netlink.c
Normal file
758
drivers/w1/w1_netlink.c
Normal file
|
@ -0,0 +1,758 @@
|
|||
/*
|
||||
* w1_netlink.c
|
||||
*
|
||||
* Copyright (c) 2003 Evgeniy Polyakov <zbr@ioremap.net>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* 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)))
|
||||
|
||||
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
/* Bundle together everything required to process a request in one memory
|
||||
* allocation.
|
||||
*/
|
||||
struct w1_cb_block {
|
||||
atomic_t refcnt;
|
||||
u32 portid; /* Sending process port ID */
|
||||
/* maximum value for first_cn->len */
|
||||
u16 maxlen;
|
||||
/* pointers to building up the reply message */
|
||||
struct cn_msg *first_cn; /* fixed once the structure is populated */
|
||||
struct cn_msg *cn; /* advances as cn_msg is appeneded */
|
||||
struct w1_netlink_msg *msg; /* advances as w1_netlink_msg is appened */
|
||||
struct w1_netlink_cmd *cmd; /* advances as cmds are appened */
|
||||
struct w1_netlink_msg *cur_msg; /* currently message being processed */
|
||||
/* copy of the original request follows */
|
||||
struct cn_msg request_cn;
|
||||
/* followed by variable length:
|
||||
* cn_msg, data (w1_netlink_msg and w1_netlink_cmd)
|
||||
* one or more struct w1_cb_node
|
||||
* reply first_cn, data (w1_netlink_msg and w1_netlink_cmd)
|
||||
*/
|
||||
};
|
||||
struct w1_cb_node {
|
||||
struct w1_async_cmd async;
|
||||
/* pointers within w1_cb_block and cn data */
|
||||
struct w1_cb_block *block;
|
||||
struct w1_netlink_msg *msg;
|
||||
struct w1_slave *sl;
|
||||
struct w1_master *dev;
|
||||
};
|
||||
|
||||
/**
|
||||
* w1_reply_len() - calculate current reply length, compare to maxlen
|
||||
* @block: block to calculate
|
||||
*
|
||||
* Calculates the current message length including possible multiple
|
||||
* cn_msg and data, excludes the first sizeof(struct cn_msg). Direclty
|
||||
* compariable to maxlen and usable to send the message.
|
||||
*/
|
||||
static u16 w1_reply_len(struct w1_cb_block *block)
|
||||
{
|
||||
if (!block->cn)
|
||||
return 0;
|
||||
return (u8 *)block->cn - (u8 *)block->first_cn + block->cn->len;
|
||||
}
|
||||
|
||||
static void w1_unref_block(struct w1_cb_block *block)
|
||||
{
|
||||
if (atomic_sub_return(1, &block->refcnt) == 0) {
|
||||
u16 len = w1_reply_len(block);
|
||||
if (len) {
|
||||
cn_netlink_send_mult(block->first_cn, len,
|
||||
block->portid, 0, GFP_KERNEL);
|
||||
}
|
||||
kfree(block);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_reply_make_space() - send message if needed to make space
|
||||
* @block: block to make space on
|
||||
* @space: how many bytes requested
|
||||
*
|
||||
* Verify there is enough room left for the caller to add "space" bytes to the
|
||||
* message, if there isn't send the message and reset.
|
||||
*/
|
||||
static void w1_reply_make_space(struct w1_cb_block *block, u16 space)
|
||||
{
|
||||
u16 len = w1_reply_len(block);
|
||||
if (len + space >= block->maxlen) {
|
||||
cn_netlink_send_mult(block->first_cn, len, block->portid, 0, GFP_KERNEL);
|
||||
block->first_cn->len = 0;
|
||||
block->cn = NULL;
|
||||
block->msg = NULL;
|
||||
block->cmd = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Early send when replies aren't bundled. */
|
||||
static void w1_netlink_check_send(struct w1_cb_block *block)
|
||||
{
|
||||
if (!(block->request_cn.flags & W1_CN_BUNDLE) && block->cn)
|
||||
w1_reply_make_space(block, block->maxlen);
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_netlink_setup_msg() - prepare to write block->msg
|
||||
* @block: block to operate on
|
||||
* @ack: determines if cn can be reused
|
||||
*
|
||||
* block->cn will be setup with the correct ack, advancing if needed
|
||||
* block->cn->len does not include space for block->msg
|
||||
* block->msg advances but remains uninitialized
|
||||
*/
|
||||
static void w1_netlink_setup_msg(struct w1_cb_block *block, u32 ack)
|
||||
{
|
||||
if (block->cn && block->cn->ack == ack) {
|
||||
block->msg = (struct w1_netlink_msg *)(block->cn->data + block->cn->len);
|
||||
} else {
|
||||
/* advance or set to data */
|
||||
if (block->cn)
|
||||
block->cn = (struct cn_msg *)(block->cn->data +
|
||||
block->cn->len);
|
||||
else
|
||||
block->cn = block->first_cn;
|
||||
|
||||
memcpy(block->cn, &block->request_cn, sizeof(*block->cn));
|
||||
block->cn->len = 0;
|
||||
block->cn->ack = ack;
|
||||
block->msg = (struct w1_netlink_msg *)block->cn->data;
|
||||
}
|
||||
}
|
||||
|
||||
/* Append cmd to msg, include cmd->data as well. This is because
|
||||
* any following data goes with the command and in the case of a read is
|
||||
* the results.
|
||||
*/
|
||||
static void w1_netlink_queue_cmd(struct w1_cb_block *block,
|
||||
struct w1_netlink_cmd *cmd)
|
||||
{
|
||||
u32 space;
|
||||
w1_reply_make_space(block, sizeof(struct cn_msg) +
|
||||
sizeof(struct w1_netlink_msg) + sizeof(*cmd) + cmd->len);
|
||||
|
||||
/* There's a status message sent after each command, so no point
|
||||
* in trying to bundle this cmd after an existing one, because
|
||||
* there won't be one. Allocate and copy over a new cn_msg.
|
||||
*/
|
||||
w1_netlink_setup_msg(block, block->request_cn.seq + 1);
|
||||
memcpy(block->msg, block->cur_msg, sizeof(*block->msg));
|
||||
block->cn->len += sizeof(*block->msg);
|
||||
block->msg->len = 0;
|
||||
block->cmd = (struct w1_netlink_cmd *)(block->msg->data);
|
||||
|
||||
space = sizeof(*cmd) + cmd->len;
|
||||
if (block->cmd != cmd)
|
||||
memcpy(block->cmd, cmd, space);
|
||||
block->cn->len += space;
|
||||
block->msg->len += space;
|
||||
}
|
||||
|
||||
/* Append req_msg and req_cmd, no other commands and no data from req_cmd are
|
||||
* copied.
|
||||
*/
|
||||
static void w1_netlink_queue_status(struct w1_cb_block *block,
|
||||
struct w1_netlink_msg *req_msg, struct w1_netlink_cmd *req_cmd,
|
||||
int error)
|
||||
{
|
||||
u16 space = sizeof(struct cn_msg) + sizeof(*req_msg) + sizeof(*req_cmd);
|
||||
w1_reply_make_space(block, space);
|
||||
w1_netlink_setup_msg(block, block->request_cn.ack);
|
||||
|
||||
memcpy(block->msg, req_msg, sizeof(*req_msg));
|
||||
block->cn->len += sizeof(*req_msg);
|
||||
block->msg->len = 0;
|
||||
block->msg->status = (u8)-error;
|
||||
if (req_cmd) {
|
||||
struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)block->msg->data;
|
||||
memcpy(cmd, req_cmd, sizeof(*cmd));
|
||||
block->cn->len += sizeof(*cmd);
|
||||
block->msg->len += sizeof(*cmd);
|
||||
cmd->len = 0;
|
||||
}
|
||||
w1_netlink_check_send(block);
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_netlink_send_error() - sends the error message now
|
||||
* @cn: original cn_msg
|
||||
* @msg: original w1_netlink_msg
|
||||
* @portid: where to send it
|
||||
* @error: error status
|
||||
*
|
||||
* Use when a block isn't available to queue the message to and cn, msg
|
||||
* might not be contiguous.
|
||||
*/
|
||||
static void w1_netlink_send_error(struct cn_msg *cn, struct w1_netlink_msg *msg,
|
||||
int portid, int error)
|
||||
{
|
||||
struct {
|
||||
struct cn_msg cn;
|
||||
struct w1_netlink_msg msg;
|
||||
} packet;
|
||||
memcpy(&packet.cn, cn, sizeof(packet.cn));
|
||||
memcpy(&packet.msg, msg, sizeof(packet.msg));
|
||||
packet.cn.len = sizeof(packet.msg);
|
||||
packet.msg.len = 0;
|
||||
packet.msg.status = (u8)-error;
|
||||
cn_netlink_send(&packet.cn, portid, 0, GFP_KERNEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* w1_netlink_send() - sends w1 netlink notifications
|
||||
* @dev: w1_master the even is associated with or for
|
||||
* @msg: w1_netlink_msg message to be sent
|
||||
*
|
||||
* This are notifications generated from the kernel.
|
||||
*/
|
||||
void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *msg)
|
||||
{
|
||||
struct {
|
||||
struct cn_msg cn;
|
||||
struct w1_netlink_msg msg;
|
||||
} packet;
|
||||
memset(&packet, 0, sizeof(packet));
|
||||
|
||||
packet.cn.id.idx = CN_W1_IDX;
|
||||
packet.cn.id.val = CN_W1_VAL;
|
||||
|
||||
packet.cn.seq = dev->seq++;
|
||||
packet.cn.len = sizeof(*msg);
|
||||
|
||||
memcpy(&packet.msg, msg, sizeof(*msg));
|
||||
packet.msg.len = 0;
|
||||
|
||||
cn_netlink_send(&packet.cn, 0, 0, GFP_KERNEL);
|
||||
}
|
||||
|
||||
static void w1_send_slave(struct w1_master *dev, u64 rn)
|
||||
{
|
||||
struct w1_cb_block *block = dev->priv;
|
||||
struct w1_netlink_cmd *cache_cmd = block->cmd;
|
||||
u64 *data;
|
||||
|
||||
w1_reply_make_space(block, sizeof(*data));
|
||||
|
||||
/* Add cmd back if the packet was sent */
|
||||
if (!block->cmd) {
|
||||
cache_cmd->len = 0;
|
||||
w1_netlink_queue_cmd(block, cache_cmd);
|
||||
}
|
||||
|
||||
data = (u64 *)(block->cmd->data + block->cmd->len);
|
||||
|
||||
*data = rn;
|
||||
block->cn->len += sizeof(*data);
|
||||
block->msg->len += sizeof(*data);
|
||||
block->cmd->len += sizeof(*data);
|
||||
}
|
||||
|
||||
static void w1_found_send_slave(struct w1_master *dev, u64 rn)
|
||||
{
|
||||
/* update kernel slave list */
|
||||
w1_slave_found(dev, rn);
|
||||
|
||||
w1_send_slave(dev, rn);
|
||||
}
|
||||
|
||||
/* Get the current slave list, or search (with or without alarm) */
|
||||
static int w1_get_slaves(struct w1_master *dev, struct w1_netlink_cmd *req_cmd)
|
||||
{
|
||||
struct w1_slave *sl;
|
||||
|
||||
req_cmd->len = 0;
|
||||
w1_netlink_queue_cmd(dev->priv, req_cmd);
|
||||
|
||||
if (req_cmd->cmd == W1_CMD_LIST_SLAVES) {
|
||||
u64 rn;
|
||||
mutex_lock(&dev->list_mutex);
|
||||
list_for_each_entry(sl, &dev->slist, w1_slave_entry) {
|
||||
memcpy(&rn, &sl->reg_num, sizeof(rn));
|
||||
w1_send_slave(dev, rn);
|
||||
}
|
||||
mutex_unlock(&dev->list_mutex);
|
||||
} else {
|
||||
w1_search_process_cb(dev, req_cmd->cmd == W1_CMD_ALARM_SEARCH ?
|
||||
W1_ALARM_SEARCH : W1_SEARCH, w1_found_send_slave);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int w1_process_command_io(struct w1_master *dev,
|
||||
struct w1_netlink_cmd *cmd)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
switch (cmd->cmd) {
|
||||
case W1_CMD_TOUCH:
|
||||
w1_touch_block(dev, cmd->data, cmd->len);
|
||||
w1_netlink_queue_cmd(dev->priv, cmd);
|
||||
break;
|
||||
case W1_CMD_READ:
|
||||
w1_read_block(dev, cmd->data, cmd->len);
|
||||
w1_netlink_queue_cmd(dev->priv, cmd);
|
||||
break;
|
||||
case W1_CMD_WRITE:
|
||||
w1_write_block(dev, cmd->data, cmd->len);
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int w1_process_command_addremove(struct w1_master *dev,
|
||||
struct w1_netlink_cmd *cmd)
|
||||
{
|
||||
struct w1_slave *sl;
|
||||
int err = 0;
|
||||
struct w1_reg_num *id;
|
||||
|
||||
if (cmd->len != sizeof(*id))
|
||||
return -EINVAL;
|
||||
|
||||
id = (struct w1_reg_num *)cmd->data;
|
||||
|
||||
sl = w1_slave_search_device(dev, id);
|
||||
switch (cmd->cmd) {
|
||||
case W1_CMD_SLAVE_ADD:
|
||||
if (sl)
|
||||
err = -EINVAL;
|
||||
else
|
||||
err = w1_attach_slave_device(dev, id);
|
||||
break;
|
||||
case W1_CMD_SLAVE_REMOVE:
|
||||
if (sl)
|
||||
w1_slave_detach(sl);
|
||||
else
|
||||
err = -EINVAL;
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int w1_process_command_master(struct w1_master *dev,
|
||||
struct w1_netlink_cmd *req_cmd)
|
||||
{
|
||||
int err = -EINVAL;
|
||||
|
||||
/* drop bus_mutex for search (does it's own locking), and add/remove
|
||||
* which doesn't use the bus
|
||||
*/
|
||||
switch (req_cmd->cmd) {
|
||||
case W1_CMD_SEARCH:
|
||||
case W1_CMD_ALARM_SEARCH:
|
||||
case W1_CMD_LIST_SLAVES:
|
||||
mutex_unlock(&dev->bus_mutex);
|
||||
err = w1_get_slaves(dev, req_cmd);
|
||||
mutex_lock(&dev->bus_mutex);
|
||||
break;
|
||||
case W1_CMD_READ:
|
||||
case W1_CMD_WRITE:
|
||||
case W1_CMD_TOUCH:
|
||||
err = w1_process_command_io(dev, req_cmd);
|
||||
break;
|
||||
case W1_CMD_RESET:
|
||||
err = w1_reset_bus(dev);
|
||||
break;
|
||||
case W1_CMD_SLAVE_ADD:
|
||||
case W1_CMD_SLAVE_REMOVE:
|
||||
mutex_unlock(&dev->bus_mutex);
|
||||
mutex_lock(&dev->mutex);
|
||||
err = w1_process_command_addremove(dev, req_cmd);
|
||||
mutex_unlock(&dev->mutex);
|
||||
mutex_lock(&dev->bus_mutex);
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int w1_process_command_slave(struct w1_slave *sl,
|
||||
struct w1_netlink_cmd *cmd)
|
||||
{
|
||||
dev_dbg(&sl->master->dev, "%s: %02x.%012llx.%02x: cmd=%02x, len=%u.\n",
|
||||
__func__, sl->reg_num.family, (unsigned long long)sl->reg_num.id,
|
||||
sl->reg_num.crc, cmd->cmd, cmd->len);
|
||||
|
||||
return w1_process_command_io(sl->master, cmd);
|
||||
}
|
||||
|
||||
static int w1_process_command_root(struct cn_msg *req_cn, u32 portid)
|
||||
{
|
||||
struct w1_master *dev;
|
||||
struct cn_msg *cn;
|
||||
struct w1_netlink_msg *msg;
|
||||
u32 *id;
|
||||
|
||||
cn = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
||||
if (!cn)
|
||||
return -ENOMEM;
|
||||
|
||||
cn->id.idx = CN_W1_IDX;
|
||||
cn->id.val = CN_W1_VAL;
|
||||
|
||||
cn->seq = req_cn->seq;
|
||||
cn->ack = req_cn->seq + 1;
|
||||
cn->len = sizeof(struct w1_netlink_msg);
|
||||
msg = (struct w1_netlink_msg *)cn->data;
|
||||
|
||||
msg->type = W1_LIST_MASTERS;
|
||||
msg->status = 0;
|
||||
msg->len = 0;
|
||||
id = (u32 *)msg->data;
|
||||
|
||||
mutex_lock(&w1_mlock);
|
||||
list_for_each_entry(dev, &w1_masters, w1_master_entry) {
|
||||
if (cn->len + sizeof(*id) > PAGE_SIZE - sizeof(struct cn_msg)) {
|
||||
cn_netlink_send(cn, portid, 0, GFP_KERNEL);
|
||||
cn->len = sizeof(struct w1_netlink_msg);
|
||||
msg->len = 0;
|
||||
id = (u32 *)msg->data;
|
||||
}
|
||||
|
||||
*id = dev->id;
|
||||
msg->len += sizeof(*id);
|
||||
cn->len += sizeof(*id);
|
||||
id++;
|
||||
}
|
||||
cn_netlink_send(cn, portid, 0, GFP_KERNEL);
|
||||
mutex_unlock(&w1_mlock);
|
||||
|
||||
kfree(cn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void w1_process_cb(struct w1_master *dev, struct w1_async_cmd *async_cmd)
|
||||
{
|
||||
struct w1_cb_node *node = container_of(async_cmd, struct w1_cb_node,
|
||||
async);
|
||||
u16 mlen = node->msg->len;
|
||||
u16 len;
|
||||
int err = 0;
|
||||
struct w1_slave *sl = node->sl;
|
||||
struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)node->msg->data;
|
||||
|
||||
mutex_lock(&dev->bus_mutex);
|
||||
dev->priv = node->block;
|
||||
if (sl && w1_reset_select_slave(sl))
|
||||
err = -ENODEV;
|
||||
node->block->cur_msg = node->msg;
|
||||
|
||||
while (mlen && !err) {
|
||||
if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen) {
|
||||
err = -E2BIG;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sl)
|
||||
err = w1_process_command_slave(sl, cmd);
|
||||
else
|
||||
err = w1_process_command_master(dev, cmd);
|
||||
w1_netlink_check_send(node->block);
|
||||
|
||||
w1_netlink_queue_status(node->block, node->msg, cmd, err);
|
||||
err = 0;
|
||||
|
||||
len = sizeof(*cmd) + cmd->len;
|
||||
cmd = (struct w1_netlink_cmd *)((u8 *)cmd + len);
|
||||
mlen -= len;
|
||||
}
|
||||
|
||||
if (!cmd || err)
|
||||
w1_netlink_queue_status(node->block, node->msg, cmd, err);
|
||||
|
||||
/* ref taken in w1_search_slave or w1_search_master_id when building
|
||||
* the block
|
||||
*/
|
||||
if (sl)
|
||||
w1_unref_slave(sl);
|
||||
else
|
||||
atomic_dec(&dev->refcnt);
|
||||
dev->priv = NULL;
|
||||
mutex_unlock(&dev->bus_mutex);
|
||||
|
||||
mutex_lock(&dev->list_mutex);
|
||||
list_del(&async_cmd->async_entry);
|
||||
mutex_unlock(&dev->list_mutex);
|
||||
|
||||
w1_unref_block(node->block);
|
||||
}
|
||||
|
||||
static void w1_list_count_cmds(struct w1_netlink_msg *msg, int *cmd_count,
|
||||
u16 *slave_len)
|
||||
{
|
||||
struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)msg->data;
|
||||
u16 mlen = msg->len;
|
||||
u16 len;
|
||||
int slave_list = 0;
|
||||
while (mlen) {
|
||||
if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen)
|
||||
break;
|
||||
|
||||
switch (cmd->cmd) {
|
||||
case W1_CMD_SEARCH:
|
||||
case W1_CMD_ALARM_SEARCH:
|
||||
case W1_CMD_LIST_SLAVES:
|
||||
++slave_list;
|
||||
}
|
||||
++*cmd_count;
|
||||
len = sizeof(*cmd) + cmd->len;
|
||||
cmd = (struct w1_netlink_cmd *)((u8 *)cmd + len);
|
||||
mlen -= len;
|
||||
}
|
||||
|
||||
if (slave_list) {
|
||||
struct w1_master *dev = w1_search_master_id(msg->id.mst.id);
|
||||
if (dev) {
|
||||
/* Bytes, and likely an overstimate, and if it isn't
|
||||
* the results can still be split between packets.
|
||||
*/
|
||||
*slave_len += sizeof(struct w1_reg_num) * slave_list *
|
||||
(dev->slave_count + dev->max_slave_count);
|
||||
/* search incremented it */
|
||||
atomic_dec(&dev->refcnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void w1_cn_callback(struct cn_msg *cn, struct netlink_skb_parms *nsp)
|
||||
{
|
||||
struct w1_netlink_msg *msg = (struct w1_netlink_msg *)(cn + 1);
|
||||
struct w1_slave *sl;
|
||||
struct w1_master *dev;
|
||||
u16 msg_len;
|
||||
u16 slave_len = 0;
|
||||
int err = 0;
|
||||
struct w1_cb_block *block = NULL;
|
||||
struct w1_cb_node *node = NULL;
|
||||
int node_count = 0;
|
||||
int cmd_count = 0;
|
||||
|
||||
/* If any unknown flag is set let the application know, that way
|
||||
* applications can detect the absence of features in kernels that
|
||||
* don't know about them. http://lwn.net/Articles/587527/
|
||||
*/
|
||||
if (cn->flags & ~(W1_CN_BUNDLE)) {
|
||||
w1_netlink_send_error(cn, msg, nsp->portid, -EINVAL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Count the number of master or slave commands there are to allocate
|
||||
* space for one cb_node each.
|
||||
*/
|
||||
msg_len = cn->len;
|
||||
while (msg_len && !err) {
|
||||
if (msg->len + sizeof(struct w1_netlink_msg) > msg_len) {
|
||||
err = -E2BIG;
|
||||
break;
|
||||
}
|
||||
|
||||
/* count messages for nodes and allocate any additional space
|
||||
* required for slave lists
|
||||
*/
|
||||
if (msg->type == W1_MASTER_CMD || msg->type == W1_SLAVE_CMD) {
|
||||
++node_count;
|
||||
w1_list_count_cmds(msg, &cmd_count, &slave_len);
|
||||
}
|
||||
|
||||
msg_len -= sizeof(struct w1_netlink_msg) + msg->len;
|
||||
msg = (struct w1_netlink_msg *)(((u8 *)msg) +
|
||||
sizeof(struct w1_netlink_msg) + msg->len);
|
||||
}
|
||||
msg = (struct w1_netlink_msg *)(cn + 1);
|
||||
if (node_count) {
|
||||
int size;
|
||||
u16 reply_size = sizeof(*cn) + cn->len + slave_len;
|
||||
if (cn->flags & W1_CN_BUNDLE) {
|
||||
/* bundling duplicats some of the messages */
|
||||
reply_size += 2 * cmd_count * (sizeof(struct cn_msg) +
|
||||
sizeof(struct w1_netlink_msg) +
|
||||
sizeof(struct w1_netlink_cmd));
|
||||
}
|
||||
reply_size = MIN(CONNECTOR_MAX_MSG_SIZE, reply_size);
|
||||
|
||||
/* allocate space for the block, a copy of the original message,
|
||||
* one node per cmd to point into the original message,
|
||||
* space for replies which is the original message size plus
|
||||
* space for any list slave data and status messages
|
||||
* cn->len doesn't include itself which is part of the block
|
||||
* */
|
||||
size = /* block + original message */
|
||||
sizeof(struct w1_cb_block) + sizeof(*cn) + cn->len +
|
||||
/* space for nodes */
|
||||
node_count * sizeof(struct w1_cb_node) +
|
||||
/* replies */
|
||||
sizeof(struct cn_msg) + reply_size;
|
||||
block = kzalloc(size, GFP_KERNEL);
|
||||
if (!block) {
|
||||
/* if the system is already out of memory,
|
||||
* (A) will this work, and (B) would it be better
|
||||
* to not try?
|
||||
*/
|
||||
w1_netlink_send_error(cn, msg, nsp->portid, -ENOMEM);
|
||||
return;
|
||||
}
|
||||
atomic_set(&block->refcnt, 1);
|
||||
block->portid = nsp->portid;
|
||||
memcpy(&block->request_cn, cn, sizeof(*cn) + cn->len);
|
||||
node = (struct w1_cb_node *)(block->request_cn.data + cn->len);
|
||||
|
||||
/* Sneeky, when not bundling, reply_size is the allocated space
|
||||
* required for the reply, cn_msg isn't part of maxlen so
|
||||
* it should be reply_size - sizeof(struct cn_msg), however
|
||||
* when checking if there is enough space, w1_reply_make_space
|
||||
* is called with the full message size including cn_msg,
|
||||
* because it isn't known at that time if an additional cn_msg
|
||||
* will need to be allocated. So an extra cn_msg is added
|
||||
* above in "size".
|
||||
*/
|
||||
block->maxlen = reply_size;
|
||||
block->first_cn = (struct cn_msg *)(node + node_count);
|
||||
memset(block->first_cn, 0, sizeof(*block->first_cn));
|
||||
}
|
||||
|
||||
msg_len = cn->len;
|
||||
while (msg_len && !err) {
|
||||
|
||||
dev = NULL;
|
||||
sl = NULL;
|
||||
|
||||
if (msg->len + sizeof(struct w1_netlink_msg) > msg_len) {
|
||||
err = -E2BIG;
|
||||
break;
|
||||
}
|
||||
|
||||
/* execute on this thread, no need to process later */
|
||||
if (msg->type == W1_LIST_MASTERS) {
|
||||
err = w1_process_command_root(cn, nsp->portid);
|
||||
goto out_cont;
|
||||
}
|
||||
|
||||
/* All following message types require additional data,
|
||||
* check here before references are taken.
|
||||
*/
|
||||
if (!msg->len) {
|
||||
err = -EPROTO;
|
||||
goto out_cont;
|
||||
}
|
||||
|
||||
/* both search calls take references */
|
||||
if (msg->type == W1_MASTER_CMD) {
|
||||
dev = w1_search_master_id(msg->id.mst.id);
|
||||
} else if (msg->type == W1_SLAVE_CMD) {
|
||||
sl = w1_search_slave((struct w1_reg_num *)msg->id.id);
|
||||
if (sl)
|
||||
dev = sl->master;
|
||||
} else {
|
||||
pr_notice("%s: cn: %x.%x, wrong type: %u, len: %u.\n",
|
||||
__func__, cn->id.idx, cn->id.val,
|
||||
msg->type, msg->len);
|
||||
err = -EPROTO;
|
||||
goto out_cont;
|
||||
}
|
||||
|
||||
if (!dev) {
|
||||
err = -ENODEV;
|
||||
goto out_cont;
|
||||
}
|
||||
|
||||
err = 0;
|
||||
|
||||
atomic_inc(&block->refcnt);
|
||||
node->async.cb = w1_process_cb;
|
||||
node->block = block;
|
||||
node->msg = (struct w1_netlink_msg *)((u8 *)&block->request_cn +
|
||||
(size_t)((u8 *)msg - (u8 *)cn));
|
||||
node->sl = sl;
|
||||
node->dev = dev;
|
||||
|
||||
mutex_lock(&dev->list_mutex);
|
||||
list_add_tail(&node->async.async_entry, &dev->async_list);
|
||||
wake_up_process(dev->thread);
|
||||
mutex_unlock(&dev->list_mutex);
|
||||
++node;
|
||||
|
||||
out_cont:
|
||||
/* Can't queue because that modifies block and another
|
||||
* thread could be processing the messages by now and
|
||||
* there isn't a lock, send directly.
|
||||
*/
|
||||
if (err)
|
||||
w1_netlink_send_error(cn, msg, nsp->portid, err);
|
||||
msg_len -= sizeof(struct w1_netlink_msg) + msg->len;
|
||||
msg = (struct w1_netlink_msg *)(((u8 *)msg) +
|
||||
sizeof(struct w1_netlink_msg) + msg->len);
|
||||
|
||||
/*
|
||||
* Let's allow requests for nonexisting devices.
|
||||
*/
|
||||
if (err == -ENODEV)
|
||||
err = 0;
|
||||
}
|
||||
if (block)
|
||||
w1_unref_block(block);
|
||||
}
|
||||
|
||||
int w1_init_netlink(void)
|
||||
{
|
||||
struct cb_id w1_id = {.idx = CN_W1_IDX, .val = CN_W1_VAL};
|
||||
|
||||
return cn_add_callback(&w1_id, "w1", &w1_cn_callback);
|
||||
}
|
||||
|
||||
void w1_fini_netlink(void)
|
||||
{
|
||||
struct cb_id w1_id = {.idx = CN_W1_IDX, .val = CN_W1_VAL};
|
||||
|
||||
cn_del_callback(&w1_id);
|
||||
}
|
||||
#else
|
||||
void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *cn)
|
||||
{
|
||||
}
|
||||
|
||||
int w1_init_netlink(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void w1_fini_netlink(void)
|
||||
{
|
||||
}
|
||||
#endif
|
147
drivers/w1/w1_netlink.h
Normal file
147
drivers/w1/w1_netlink.h
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* w1_netlink.h
|
||||
*
|
||||
* Copyright (c) 2003 Evgeniy Polyakov <zbr@ioremap.net>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* 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_cn_msg_flags - bitfield flags for struct cn_msg.flags
|
||||
*
|
||||
* @W1_CN_BUNDLE: Request bundling replies into fewer messagse. Be prepared
|
||||
* to handle multiple struct cn_msg, struct w1_netlink_msg, and
|
||||
* struct w1_netlink_cmd in one packet.
|
||||
*/
|
||||
enum w1_cn_msg_flags {
|
||||
W1_CN_BUNDLE = 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum w1_netlink_message_types - message type
|
||||
*
|
||||
* @W1_SLAVE_ADD: notification that a slave device was added
|
||||
* @W1_SLAVE_REMOVE: notification that a slave device was removed
|
||||
* @W1_MASTER_ADD: notification that a new bus master was added
|
||||
* @W1_MASTER_REMOVE: notification that a bus masterwas removed
|
||||
* @W1_MASTER_CMD: initiate operations on a specific master
|
||||
* @W1_SLAVE_CMD: sends reset, selects the slave, then does a read/write/touch
|
||||
* operation
|
||||
* @W1_LIST_MASTERS: used to determine the bus master identifiers
|
||||
*/
|
||||
enum w1_netlink_message_types {
|
||||
W1_SLAVE_ADD = 0,
|
||||
W1_SLAVE_REMOVE,
|
||||
W1_MASTER_ADD,
|
||||
W1_MASTER_REMOVE,
|
||||
W1_MASTER_CMD,
|
||||
W1_SLAVE_CMD,
|
||||
W1_LIST_MASTERS,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct w1_netlink_msg - holds w1 message type, id, and result
|
||||
*
|
||||
* @type: one of enum w1_netlink_message_types
|
||||
* @status: kernel feedback for success 0 or errno failure value
|
||||
* @len: length of data following w1_netlink_msg
|
||||
* @id: union holding master bus id (msg.id) and slave device id (id[8]).
|
||||
* @data: start address of any following data
|
||||
*
|
||||
* The base message structure for w1 messages over netlink.
|
||||
* The netlink connector data sequence is, struct nlmsghdr, struct cn_msg,
|
||||
* then one or more struct w1_netlink_msg (each with optional data).
|
||||
*/
|
||||
struct w1_netlink_msg
|
||||
{
|
||||
__u8 type;
|
||||
__u8 status;
|
||||
__u16 len;
|
||||
union {
|
||||
__u8 id[8];
|
||||
struct w1_mst {
|
||||
__u32 id;
|
||||
__u32 res;
|
||||
} mst;
|
||||
} id;
|
||||
__u8 data[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* enum w1_commands - commands available for master or slave operations
|
||||
*
|
||||
* @W1_CMD_READ: read len bytes
|
||||
* @W1_CMD_WRITE: write len bytes
|
||||
* @W1_CMD_SEARCH: initiate a standard search, returns only the slave
|
||||
* devices found during that search
|
||||
* @W1_CMD_ALARM_SEARCH: search for devices that are currently alarming
|
||||
* @W1_CMD_TOUCH: Touches a series of bytes.
|
||||
* @W1_CMD_RESET: sends a bus reset on the given master
|
||||
* @W1_CMD_SLAVE_ADD: adds a slave to the given master,
|
||||
* 8 byte slave id at data[0]
|
||||
* @W1_CMD_SLAVE_REMOVE: removes a slave to the given master,
|
||||
* 8 byte slave id at data[0]
|
||||
* @W1_CMD_LIST_SLAVES: list of slaves registered on this master
|
||||
* @W1_CMD_MAX: number of available commands
|
||||
*/
|
||||
enum w1_commands {
|
||||
W1_CMD_READ = 0,
|
||||
W1_CMD_WRITE,
|
||||
W1_CMD_SEARCH,
|
||||
W1_CMD_ALARM_SEARCH,
|
||||
W1_CMD_TOUCH,
|
||||
W1_CMD_RESET,
|
||||
W1_CMD_SLAVE_ADD,
|
||||
W1_CMD_SLAVE_REMOVE,
|
||||
W1_CMD_LIST_SLAVES,
|
||||
W1_CMD_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* struct w1_netlink_cmd - holds the command and data
|
||||
*
|
||||
* @cmd: one of enum w1_commands
|
||||
* @res: reserved
|
||||
* @len: length of data following w1_netlink_cmd
|
||||
* @data: start address of any following data
|
||||
*
|
||||
* One or more struct w1_netlink_cmd is placed starting at w1_netlink_msg.data
|
||||
* each with optional data.
|
||||
*/
|
||||
struct w1_netlink_cmd
|
||||
{
|
||||
__u8 cmd;
|
||||
__u8 res;
|
||||
__u16 len;
|
||||
__u8 data[0];
|
||||
};
|
||||
|
||||
#ifdef __KERNEL__
|
||||
|
||||
void w1_netlink_send(struct w1_master *, struct w1_netlink_msg *);
|
||||
int w1_init_netlink(void);
|
||||
void w1_fini_netlink(void);
|
||||
|
||||
#endif /* __KERNEL__ */
|
||||
#endif /* __W1_NETLINK_H */
|
Loading…
Add table
Add a link
Reference in a new issue