diff options
Diffstat (limited to 'drivers/hwmon')
-rw-r--r-- | drivers/hwmon/Kconfig | 36 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 3 | ||||
-rw-r--r-- | drivers/hwmon/hp_accel.c | 124 | ||||
-rw-r--r-- | drivers/hwmon/lis3lv02d.c | 288 | ||||
-rw-r--r-- | drivers/hwmon/lis3lv02d.h | 20 | ||||
-rw-r--r-- | drivers/hwmon/lis3lv02d_spi.c | 114 | ||||
-rw-r--r-- | drivers/hwmon/lm95241.c | 527 | ||||
-rw-r--r-- | drivers/hwmon/ltc4215.c | 364 |
8 files changed, 1268 insertions, 208 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 51ff9b3d7ea2..ce52bf2f235e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -571,6 +571,17 @@ config SENSORS_LM93 This driver can also be built as a module. If so, the module will be called lm93. +config SENSORS_LTC4215 + tristate "Linear Technology LTC4215" + depends on I2C && EXPERIMENTAL + default n + help + If you say yes here you get support for Linear Technology LTC4215 + Hot Swap Controller I2C interface. + + This driver can also be built as a module. If so, the module will + be called ltc4215. + config SENSORS_LTC4245 tristate "Linear Technology LTC4245" depends on I2C && EXPERIMENTAL @@ -582,6 +593,15 @@ config SENSORS_LTC4245 This driver can also be built as a module. If so, the module will be called ltc4245. +config SENSORS_LM95241 + tristate "National Semiconductor LM95241 sensor chip" + depends on I2C + help + If you say yes here you get support for LM95241 sensor chip. + + This driver can also be built as a module. If so, the module + will be called lm95241. + config SENSORS_MAX1111 tristate "Maxim MAX1111 Multichannel, Serial 8-bit ADC chip" depends on SPI_MASTER @@ -912,6 +932,22 @@ config SENSORS_LIS3LV02D Say Y here if you have an applicable laptop and want to experience the awesome power of lis3lv02d. +config SENSORS_LIS3_SPI + tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)" + depends on !ACPI && SPI_MASTER && INPUT + default n + help + This driver provides support for the LIS3LV02Dx accelerometer connected + via SPI. The accelerometer data is readable via + /sys/devices/platform/lis3lv02d. + + This driver also provides an absolute input class device, allowing + the laptop to act as a pinball machine-esque joystick. + + This driver can also be built as modules. If so, the core module + will be called lis3lv02d and a specific module for the SPI transport + is called lis3lv02d_spi. + config SENSORS_APPLESMC tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" depends on INPUT && X86 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index e332d6267920..3a6b1f06f8f4 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o +obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d.o lis3lv02d_spi.o obj-$(CONFIG_SENSORS_LM63) += lm63.o obj-$(CONFIG_SENSORS_LM70) += lm70.o obj-$(CONFIG_SENSORS_LM75) += lm75.o @@ -64,6 +65,8 @@ obj-$(CONFIG_SENSORS_LM87) += lm87.o obj-$(CONFIG_SENSORS_LM90) += lm90.o obj-$(CONFIG_SENSORS_LM92) += lm92.o obj-$(CONFIG_SENSORS_LM93) += lm93.o +obj-$(CONFIG_SENSORS_LM95241) += lm95241.o +obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o obj-$(CONFIG_SENSORS_MAX1111) += max1111.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o diff --git a/drivers/hwmon/hp_accel.c b/drivers/hwmon/hp_accel.c index 29c83b5b9697..55d3dc565be6 100644 --- a/drivers/hwmon/hp_accel.c +++ b/drivers/hwmon/hp_accel.c @@ -85,25 +85,31 @@ MODULE_DEVICE_TABLE(acpi, lis3lv02d_device_ids); /** * lis3lv02d_acpi_init - ACPI _INI method: initialize the device. - * @handle: the handle of the device + * @lis3: pointer to the device struct * - * Returns AE_OK on success. + * Returns 0 on success. */ -acpi_status lis3lv02d_acpi_init(acpi_handle handle) +int lis3lv02d_acpi_init(struct lis3lv02d *lis3) { - return acpi_evaluate_object(handle, METHOD_NAME__INI, NULL, NULL); + struct acpi_device *dev = lis3->bus_priv; + if (acpi_evaluate_object(dev->handle, METHOD_NAME__INI, + NULL, NULL) != AE_OK) + return -EINVAL; + + return 0; } /** * lis3lv02d_acpi_read - ACPI ALRD method: read a register - * @handle: the handle of the device + * @lis3: pointer to the device struct * @reg: the register to read * @ret: result of the operation * - * Returns AE_OK on success. + * Returns 0 on success. */ -acpi_status lis3lv02d_acpi_read(acpi_handle handle, int reg, u8 *ret) +int lis3lv02d_acpi_read(struct lis3lv02d *lis3, int reg, u8 *ret) { + struct acpi_device *dev = lis3->bus_priv; union acpi_object arg0 = { ACPI_TYPE_INTEGER }; struct acpi_object_list args = { 1, &arg0 }; unsigned long long lret; @@ -111,21 +117,22 @@ acpi_status lis3lv02d_acpi_read(acpi_handle handle, int reg, u8 *ret) arg0.integer.value = reg; - status = acpi_evaluate_integer(handle, "ALRD", &args, &lret); + status = acpi_evaluate_integer(dev->handle, "ALRD", &args, &lret); *ret = lret; - return status; + return (status != AE_OK) ? -EINVAL : 0; } /** * lis3lv02d_acpi_write - ACPI ALWR method: write to a register - * @handle: the handle of the device + * @lis3: pointer to the device struct * @reg: the register to write to * @val: the value to write * - * Returns AE_OK on success. + * Returns 0 on success. */ -acpi_status lis3lv02d_acpi_write(acpi_handle handle, int reg, u8 val) +int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val) { + struct acpi_device *dev = lis3->bus_priv; unsigned long long ret; /* Not used when writting */ union acpi_object in_obj[2]; struct acpi_object_list args = { 2, in_obj }; @@ -135,12 +142,15 @@ acpi_status lis3lv02d_acpi_write(acpi_handle handle, int reg, u8 val) in_obj[1].type = ACPI_TYPE_INTEGER; in_obj[1].integer.value = val; - return acpi_evaluate_integer(handle, "ALWR", &args, &ret); + if (acpi_evaluate_integer(dev->handle, "ALWR", &args, &ret) != AE_OK) + return -EINVAL; + + return 0; } static int lis3lv02d_dmi_matched(const struct dmi_system_id *dmi) { - adev.ac = *((struct axis_conversion *)dmi->driver_data); + lis3_dev.ac = *((struct axis_conversion *)dmi->driver_data); printk(KERN_INFO DRIVER_NAME ": hardware type %s found.\n", dmi->ident); return 1; @@ -187,6 +197,7 @@ static struct dmi_system_id lis3lv02d_dmi_ids[] = { AXIS_DMI_MATCH("NC2510", "HP Compaq 2510", y_inverted), AXIS_DMI_MATCH("NC8510", "HP Compaq 8510", xy_swap_inverted), AXIS_DMI_MATCH("HP2133", "HP 2133", xy_rotated_left), + AXIS_DMI_MATCH("HP2140", "HP 2140", xy_swap_inverted), AXIS_DMI_MATCH("NC653x", "HP Compaq 653", xy_rotated_left_usd), AXIS_DMI_MATCH("NC673x", "HP Compaq 673", xy_rotated_left_usd), AXIS_DMI_MATCH("NC651xx", "HP Compaq 651", xy_rotated_right), @@ -201,6 +212,8 @@ static struct dmi_system_id lis3lv02d_dmi_ids[] = { PRODUCT_NAME, "HP Pavilion dv5", BOARD_NAME, "3600", y_inverted), + AXIS_DMI_MATCH("DV7", "HP Pavilion dv7", x_inverted), + AXIS_DMI_MATCH("HP8710", "HP Compaq 8710", y_inverted), { NULL, } /* Laptop models without axis info (yet): * "NC6910" "HP Compaq 6910" @@ -214,7 +227,7 @@ static struct dmi_system_id lis3lv02d_dmi_ids[] = { static void hpled_set(struct delayed_led_classdev *led_cdev, enum led_brightness value) { - acpi_handle handle = adev.device->handle; + struct acpi_device *dev = lis3_dev.bus_priv; unsigned long long ret; /* Not used when writing */ union acpi_object in_obj[1]; struct acpi_object_list args = { 1, in_obj }; @@ -222,7 +235,7 @@ static void hpled_set(struct delayed_led_classdev *led_cdev, enum led_brightness in_obj[0].type = ACPI_TYPE_INTEGER; in_obj[0].integer.value = !!value; - acpi_evaluate_integer(handle, "ALED", &args, &ret); + acpi_evaluate_integer(dev->handle, "ALED", &args, &ret); } static struct delayed_led_classdev hpled_led = { @@ -254,28 +267,11 @@ static void lis3lv02d_enum_resources(struct acpi_device *device) acpi_status status; status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, - lis3lv02d_get_resource, &adev.irq); + lis3lv02d_get_resource, &lis3_dev.irq); if (ACPI_FAILURE(status)) printk(KERN_DEBUG DRIVER_NAME ": Error getting resources\n"); } -static s16 lis3lv02d_read_16(acpi_handle handle, int reg) -{ - u8 lo, hi; - - adev.read(handle, reg - 1, &lo); - adev.read(handle, reg, &hi); - /* In "12 bit right justified" mode, bit 6, bit 7, bit 8 = bit 5 */ - return (s16)((hi << 8) | lo); -} - -static s16 lis3lv02d_read_8(acpi_handle handle, int reg) -{ - s8 lo; - adev.read(handle, reg, &lo); - return lo; -} - static int lis3lv02d_add(struct acpi_device *device) { int ret; @@ -283,51 +279,35 @@ static int lis3lv02d_add(struct acpi_device *device) if (!device) return -EINVAL; - adev.device = device; - adev.init = lis3lv02d_acpi_init; - adev.read = lis3lv02d_acpi_read; - adev.write = lis3lv02d_acpi_write; + lis3_dev.bus_priv = device; + lis3_dev.init = lis3lv02d_acpi_init; + lis3_dev.read = lis3lv02d_acpi_read; + lis3_dev.write = lis3lv02d_acpi_write; strcpy(acpi_device_name(device), DRIVER_NAME); strcpy(acpi_device_class(device), ACPI_MDPS_CLASS); - device->driver_data = &adev; - - lis3lv02d_acpi_read(device->handle, WHO_AM_I, &adev.whoami); - switch (adev.whoami) { - case LIS_DOUBLE_ID: - printk(KERN_INFO DRIVER_NAME ": 2-byte sensor found\n"); - adev.read_data = lis3lv02d_read_16; - adev.mdps_max_val = 2048; - break; - case LIS_SINGLE_ID: - printk(KERN_INFO DRIVER_NAME ": 1-byte sensor found\n"); - adev.read_data = lis3lv02d_read_8; - adev.mdps_max_val = 128; - break; - default: - printk(KERN_ERR DRIVER_NAME - ": unknown sensor type 0x%X\n", adev.whoami); - return -EINVAL; - } + device->driver_data = &lis3_dev; + + /* obtain IRQ number of our device from ACPI */ + lis3lv02d_enum_resources(device); /* If possible use a "standard" axes order */ if (dmi_check_system(lis3lv02d_dmi_ids) == 0) { printk(KERN_INFO DRIVER_NAME ": laptop model unknown, " "using default axes configuration\n"); - adev.ac = lis3lv02d_axis_normal; + lis3_dev.ac = lis3lv02d_axis_normal; } - INIT_WORK(&hpled_led.work, delayed_set_status_worker); - ret = led_classdev_register(NULL, &hpled_led.led_classdev); + /* call the core layer do its init */ + ret = lis3lv02d_init_device(&lis3_dev); if (ret) return ret; - /* obtain IRQ number of our device from ACPI */ - lis3lv02d_enum_resources(adev.device); - - ret = lis3lv02d_init_device(&adev); + INIT_WORK(&hpled_led.work, delayed_set_status_worker); + ret = led_classdev_register(NULL, &hpled_led.led_classdev); if (ret) { + lis3lv02d_joystick_disable(); + lis3lv02d_poweroff(&lis3_dev); flush_work(&hpled_led.work); - led_classdev_unregister(&hpled_led.led_classdev); return ret; } @@ -340,7 +320,7 @@ static int lis3lv02d_remove(struct acpi_device *device, int type) return -EINVAL; lis3lv02d_joystick_disable(); - lis3lv02d_poweroff(device->handle); + lis3lv02d_poweroff(&lis3_dev); flush_work(&hpled_led.work); led_classdev_unregister(&hpled_led.led_classdev); @@ -353,19 +333,19 @@ static int lis3lv02d_remove(struct acpi_device *device, int type) static int lis3lv02d_suspend(struct acpi_device *device, pm_message_t state) { /* make sure the device is off when we suspend */ - lis3lv02d_poweroff(device->handle); + lis3lv02d_poweroff(&lis3_dev); return 0; } static int lis3lv02d_resume(struct acpi_device *device) { /* put back the device in the right state (ACPI might turn it on) */ - mutex_lock(&adev.lock); - if (adev.usage > 0) - lis3lv02d_poweron(device->handle); + mutex_lock(&lis3_dev.lock); + if (lis3_dev.usage > 0) + lis3lv02d_poweron(&lis3_dev); else - lis3lv02d_poweroff(device->handle); - mutex_unlock(&adev.lock); + lis3lv02d_poweroff(&lis3_dev); + mutex_unlock(&lis3_dev.lock); return 0; } #else diff --git a/drivers/hwmon/lis3lv02d.c b/drivers/hwmon/lis3lv02d.c index 8bb2158f0453..778eb7795983 100644 --- a/drivers/hwmon/lis3lv02d.c +++ b/drivers/hwmon/lis3lv02d.c @@ -36,7 +36,6 @@ #include <linux/freezer.h> #include <linux/uaccess.h> #include <linux/miscdevice.h> -#include <acpi/acpi_drivers.h> #include <asm/atomic.h> #include "lis3lv02d.h" @@ -53,13 +52,30 @@ * joystick. */ -struct acpi_lis3lv02d adev = { - .misc_wait = __WAIT_QUEUE_HEAD_INITIALIZER(adev.misc_wait), +struct lis3lv02d lis3_dev = { + .misc_wait = __WAIT_QUEUE_HEAD_INITIALIZER(lis3_dev.misc_wait), }; -EXPORT_SYMBOL_GPL(adev); +EXPORT_SYMBOL_GPL(lis3_dev); -static int lis3lv02d_add_fs(struct acpi_device *device); +static s16 lis3lv02d_read_8(struct lis3lv02d *lis3, int reg) +{ + s8 lo; + if (lis3->read(lis3, reg, &lo) < 0) + return 0; + + return lo; +} + +static s16 lis3lv02d_read_16(struct lis3lv02d *lis3, int reg) +{ + u8 lo, hi; + + lis3->read(lis3, reg - 1, &lo); + lis3->read(lis3, reg, &hi); + /* In "12 bit right justified" mode, bit 6, bit 7, bit 8 = bit 5 */ + return (s16)((hi << 8) | lo); +} /** * lis3lv02d_get_axis - For the given axis, give the value converted @@ -78,36 +94,36 @@ static inline int lis3lv02d_get_axis(s8 axis, int hw_values[3]) /** * lis3lv02d_get_xyz - Get X, Y and Z axis values from the accelerometer - * @handle: the handle to the device - * @x: where to store the X axis value - * @y: where to store the Y axis value - * @z: where to store the Z axis value + * @lis3: pointer to the device struct + * @x: where to store the X axis value + * @y: where to store the Y axis value + * @z: where to store the Z axis value * * Note that 40Hz input device can eat up about 10% CPU at 800MHZ */ -static void lis3lv02d_get_xyz(acpi_handle handle, int *x, int *y, int *z) +static void lis3lv02d_get_xyz(struct lis3lv02d *lis3, int *x, int *y, int *z) { int position[3]; - position[0] = adev.read_data(handle, OUTX); - position[1] = adev.read_data(handle, OUTY); - position[2] = adev.read_data(handle, OUTZ); + position[0] = lis3_dev.read_data(lis3, OUTX); + position[1] = lis3_dev.read_data(lis3, OUTY); + position[2] = lis3_dev.read_data(lis3, OUTZ); - *x = lis3lv02d_get_axis(adev.ac.x, position); - *y = lis3lv02d_get_axis(adev.ac.y, position); - *z = lis3lv02d_get_axis(adev.ac.z, position); + *x = lis3lv02d_get_axis(lis3_dev.ac.x, position); + *y = lis3lv02d_get_axis(lis3_dev.ac.y, position); + *z = lis3lv02d_get_axis(lis3_dev.ac.z, position); } -void lis3lv02d_poweroff(acpi_handle handle) +void lis3lv02d_poweroff(struct lis3lv02d *lis3) { - adev.is_on = 0; + lis3_dev.is_on = 0; } EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); -void lis3lv02d_poweron(acpi_handle handle) +void lis3lv02d_poweron(struct lis3lv02d *lis3) { - adev.is_on = 1; - adev.init(handle); + lis3_dev.is_on = 1; + lis3_dev.init(lis3); } EXPORT_SYMBOL_GPL(lis3lv02d_poweron); @@ -116,13 +132,13 @@ EXPORT_SYMBOL_GPL(lis3lv02d_poweron); * device will always be on until a call to lis3lv02d_decrease_use(). Not to be * used from interrupt context. */ -static void lis3lv02d_increase_use(struct acpi_lis3lv02d *dev) +static void lis3lv02d_increase_use(struct lis3lv02d *dev) { mutex_lock(&dev->lock); dev->usage++; if (dev->usage == 1) { if (!dev->is_on) - lis3lv02d_poweron(dev->device->handle); + lis3lv02d_poweron(dev); } mutex_unlock(&dev->lock); } @@ -131,12 +147,12 @@ static void lis3lv02d_increase_use(struct acpi_lis3lv02d *dev) * To be called whenever a usage of the device is stopped. * It will make sure to turn off the device when there is not usage. */ -static void lis3lv02d_decrease_use(struct acpi_lis3lv02d *dev) +static void lis3lv02d_decrease_use(struct lis3lv02d *dev) { mutex_lock(&dev->lock); dev->usage--; if (dev->usage == 0) - lis3lv02d_poweroff(dev->device->handle); + lis3lv02d_poweroff(dev); mutex_unlock(&dev->lock); } @@ -147,10 +163,10 @@ static irqreturn_t lis302dl_interrupt(int irq, void *dummy) * the lid is closed. This leads to interrupts as soon as a little move * is done. */ - atomic_inc(&adev.count); + atomic_inc(&lis3_dev.count); - wake_up_interruptible(&adev.misc_wait); - kill_fasync(&adev.async_queue, SIGIO, POLL_IN); + wake_up_interruptible(&lis3_dev.misc_wait); + kill_fasync(&lis3_dev.async_queue, SIGIO, POLL_IN); return IRQ_HANDLED; } @@ -158,10 +174,10 @@ static int lis3lv02d_misc_open(struct inode *inode, struct file *file) { int ret; - if (test_and_set_bit(0, &adev.misc_opened)) + if (test_and_set_bit(0, &lis3_dev.misc_opened)) return -EBUSY; /* already open */ - atomic_set(&adev.count, 0); + atomic_set(&lis3_dev.count, 0); /* * The sensor can generate interrupts for free-fall and direction @@ -174,25 +190,25 @@ static int lis3lv02d_misc_open(struct inode *inode, struct file *file) * io-apic is not configurable (and generates a warning) but I keep it * in case of support for other hardware. */ - ret = request_irq(adev.irq, lis302dl_interrupt, IRQF_TRIGGER_RISING, - DRIVER_NAME, &adev); + ret = request_irq(lis3_dev.irq, lis302dl_interrupt, IRQF_TRIGGER_RISING, + DRIVER_NAME, &lis3_dev); if (ret) { - clear_bit(0, &adev.misc_opened); - printk(KERN_ERR DRIVER_NAME ": IRQ%d allocation failed\n", adev.irq); + clear_bit(0, &lis3_dev.misc_opened); + printk(KERN_ERR DRIVER_NAME ": IRQ%d allocation failed\n", lis3_dev.irq); return -EBUSY; } - lis3lv02d_increase_use(&adev); - printk("lis3: registered interrupt %d\n", adev.irq); + lis3lv02d_increase_use(&lis3_dev); + printk("lis3: registered interrupt %d\n", lis3_dev.irq); return 0; } static int lis3lv02d_misc_release(struct inode *inode, struct file *file) { - fasync_helper(-1, file, 0, &adev.async_queue); - lis3lv02d_decrease_use(&adev); - free_irq(adev.irq, &adev); - clear_bit(0, &adev.misc_opened); /* release the device */ + fasync_helper(-1, file, 0, &lis3_dev.async_queue); + lis3lv02d_decrease_use(&lis3_dev); + free_irq(lis3_dev.irq, &lis3_dev); + clear_bit(0, &lis3_dev.misc_opened); /* release the device */ return 0; } @@ -207,10 +223,10 @@ static ssize_t lis3lv02d_misc_read(struct file *file, char __user *buf, if (count < 1) return -EINVAL; - add_wait_queue(&adev.misc_wait, &wait); + add_wait_queue(&lis3_dev.misc_wait, &wait); while (true) { set_current_state(TASK_INTERRUPTIBLE); - data = atomic_xchg(&adev.count, 0); + data = atomic_xchg(&lis3_dev.count, 0); if (data) break; @@ -240,22 +256,22 @@ static ssize_t lis3lv02d_misc_read(struct file *file, char __user *buf, out: __set_current_state(TASK_RUNNING); - remove_wait_queue(&adev.misc_wait, &wait); + remove_wait_queue(&lis3_dev.misc_wait, &wait); return retval; } static unsigned int lis3lv02d_misc_poll(struct file *file, poll_table *wait) { - poll_wait(file, &adev.misc_wait, wait); - if (atomic_read(&adev.count)) + poll_wait(file, &lis3_dev.misc_wait, wait); + if (atomic_read(&lis3_dev.count)) return POLLIN | POLLRDNORM; return 0; } static int lis3lv02d_misc_fasync(int fd, struct file *file, int on) { - return fasync_helper(fd, file, on, &adev.async_queue); + return fasync_helper(fd, file, on, &lis3_dev.async_queue); } static const struct file_operations lis3lv02d_misc_fops = { @@ -283,12 +299,12 @@ static int lis3lv02d_joystick_kthread(void *data) int x, y, z; while (!kthread_should_stop()) { - lis3lv02d_get_xyz(adev.device->handle, &x, &y, &z); - input_report_abs(adev.idev, ABS_X, x - adev.xcalib); - input_report_abs(adev.idev, ABS_Y, y - adev.ycalib); - input_report_abs(adev.idev, ABS_Z, z - adev.zcalib); + lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z); + input_report_abs(lis3_dev.idev, ABS_X, x - lis3_dev.xcalib); + input_report_abs(lis3_dev.idev, ABS_Y, y - lis3_dev.ycalib); + input_report_abs(lis3_dev.idev, ABS_Z, z - lis3_dev.zcalib); - input_sync(adev.idev); + input_sync(lis3_dev.idev); try_to_freeze(); msleep_interruptible(MDPS_POLL_INTERVAL); @@ -299,11 +315,11 @@ static int lis3lv02d_joystick_kthread(void *data) static int lis3lv02d_joystick_open(struct input_dev *input) { - lis3lv02d_increase_use(&adev); - adev.kthread = kthread_run(lis3lv02d_joystick_kthread, NULL, "klis3lv02d"); - if (IS_ERR(adev.kthread)) { - lis3lv02d_decrease_use(&adev); - return PTR_ERR(adev.kthread); + lis3lv02d_increase_use(&lis3_dev); + lis3_dev.kthread = kthread_run(lis3lv02d_joystick_kthread, NULL, "klis3lv02d"); + if (IS_ERR(lis3_dev.kthread)) { + lis3lv02d_decrease_use(&lis3_dev); + return PTR_ERR(lis3_dev.kthread); } return 0; @@ -311,45 +327,46 @@ static int lis3lv02d_joystick_open(struct input_dev *input) static void lis3lv02d_joystick_close(struct input_dev *input) { - kthread_stop(adev.kthread); - lis3lv02d_decrease_use(&adev); + kthread_stop(lis3_dev.kthread); + lis3lv02d_decrease_use(&lis3_dev); } static inline void lis3lv02d_calibrate_joystick(void) { - lis3lv02d_get_xyz(adev.device->handle, &adev.xcalib, &adev.ycalib, &adev.zcalib); + lis3lv02d_get_xyz(&lis3_dev, + &lis3_dev.xcalib, &lis3_dev.ycalib, &lis3_dev.zcalib); } int lis3lv02d_joystick_enable(void) { int err; - if (adev.idev) + if (lis3_dev.idev) return -EINVAL; - adev.idev = input_allocate_device(); - if (!adev.idev) + lis3_dev.idev = input_allocate_device(); + if (!lis3_dev.idev) return -ENOMEM; lis3lv02d_calibrate_joystick(); - adev.idev->name = "ST LIS3LV02DL Accelerometer"; - adev.idev->phys = DRIVER_NAME "/input0"; - adev.idev->id.bustype = BUS_HOST; - adev.idev->id.vendor = 0; - adev.idev->dev.parent = &adev.pdev->dev; - adev.idev->open = lis3lv02d_joystick_open; - adev.idev->close = lis3lv02d_joystick_close; + lis3_dev.idev->name = "ST LIS3LV02DL Accelerometer"; + lis3_dev.idev->phys = DRIVER_NAME "/input0"; + lis3_dev.idev->id.bustype = BUS_HOST; + lis3_dev.idev->id.vendor = 0; + lis3_dev.idev->dev.parent = &lis3_dev.pdev->dev; + lis3_dev.idev->open = lis3lv02d_joystick_open; + lis3_dev.idev->close = lis3lv02d_joystick_close; - set_bit(EV_ABS, adev.idev->evbit); - input_set_abs_params(adev.idev, ABS_X, -adev.mdps_max_val, adev.mdps_max_val, 3, 3); - input_set_abs_params(adev.idev, ABS_Y, -adev.mdps_max_val, adev.mdps_max_val, 3, 3); - input_set_abs_params(adev.idev, ABS_Z, -adev.mdps_max_val, adev.mdps_max_val, 3, 3); + set_bit(EV_ABS, lis3_dev.idev->evbit); + input_set_abs_params(lis3_dev.idev, ABS_X, -lis3_dev.mdps_max_val, lis3_dev.mdps_max_val, 3, 3); + input_set_abs_params(lis3_dev.idev, ABS_Y, -lis3_dev.mdps_max_val, lis3_dev.mdps_max_val, 3, 3); + input_set_abs_params(lis3_dev.idev, ABS_Z, -lis3_dev.mdps_max_val, lis3_dev.mdps_max_val, 3, 3); - err = input_register_device(adev.idev); + err = input_register_device(lis3_dev.idev); if (err) { - input_free_device(adev.idev); - adev.idev = NULL; + input_free_device(lis3_dev.idev); + lis3_dev.idev = NULL; } return err; @@ -358,71 +375,40 @@ EXPORT_SYMBOL_GPL(lis3lv02d_joystick_enable); void lis3lv02d_joystick_disable(void) { - if (!adev.idev) + if (!lis3_dev.idev) return; misc_deregister(&lis3lv02d_misc_device); - input_unregister_device(adev.idev); - adev.idev = NULL; + input_unregister_device(lis3_dev.idev); + lis3_dev.idev = NULL; } EXPORT_SYMBOL_GPL(lis3lv02d_joystick_disable); -/* - * Initialise the accelerometer and the various subsystems. - * Should be rather independant of the bus system. - */ -int lis3lv02d_init_device(struct acpi_lis3lv02d *dev) -{ - mutex_init(&dev->lock); - lis3lv02d_add_fs(dev->device); - lis3lv02d_increase_use(dev); - - if (lis3lv02d_joystick_enable()) - printk(KERN_ERR DRIVER_NAME ": joystick initialization failed\n"); - - printk("lis3_init_device: irq %d\n", dev->irq); - - /* if we did not get an IRQ from ACPI - we have nothing more to do */ - if (!dev->irq) { - printk(KERN_ERR DRIVER_NAME - ": No IRQ in ACPI. Disabling /dev/freefall\n"); - goto out; - } - - printk("lis3: registering device\n"); - if (misc_register(&lis3lv02d_misc_device)) - printk(KERN_ERR DRIVER_NAME ": misc_register failed\n"); -out: - lis3lv02d_decrease_use(dev); - return 0; -} -EXPORT_SYMBOL_GPL(lis3lv02d_init_device); - /* Sysfs stuff */ static ssize_t lis3lv02d_position_show(struct device *dev, struct device_attribute *attr, char *buf) { int x, y, z; - lis3lv02d_increase_use(&adev); - lis3lv02d_get_xyz(adev.device->handle, &x, &y, &z); - lis3lv02d_decrease_use(&adev); + lis3lv02d_increase_use(&lis3_dev); + lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z); + lis3lv02d_decrease_use(&lis3_dev); return sprintf(buf, "(%d,%d,%d)\n", x, y, z); } static ssize_t lis3lv02d_calibrate_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "(%d,%d,%d)\n", adev.xcalib, adev.ycalib, adev.zcalib); + return sprintf(buf, "(%d,%d,%d)\n", lis3_dev.xcalib, lis3_dev.ycalib, lis3_dev.zcalib); } static ssize_t lis3lv02d_calibrate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - lis3lv02d_increase_use(&adev); + lis3lv02d_increase_use(&lis3_dev); lis3lv02d_calibrate_joystick(); - lis3lv02d_decrease_use(&adev); + lis3lv02d_decrease_use(&lis3_dev); return count; } @@ -434,9 +420,9 @@ static ssize_t lis3lv02d_rate_show(struct device *dev, u8 ctrl; int val; - lis3lv02d_increase_use(&adev); - adev.read(adev.device->handle, CTRL_REG1, &ctrl); - lis3lv02d_decrease_use(&adev); + lis3lv02d_increase_use(&lis3_dev); + lis3_dev.read(&lis3_dev, CTRL_REG1, &ctrl); + lis3lv02d_decrease_use(&lis3_dev); val = (ctrl & (CTRL1_DF0 | CTRL1_DF1)) >> 4; return sprintf(buf, "%d\n", lis3lv02dl_df_val[val]); } @@ -458,23 +444,73 @@ static struct attribute_group lis3lv02d_attribute_group = { }; -static int lis3lv02d_add_fs(struct acpi_device *device) +static int lis3lv02d_add_fs(struct lis3lv02d *lis3) { - adev.pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0); - if (IS_ERR(adev.pdev)) - return PTR_ERR(adev.pdev); + lis3_dev.pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0); + if (IS_ERR(lis3_dev.pdev)) + return PTR_ERR(lis3_dev.pdev); - return sysfs_create_group(&adev.pdev->dev.kobj, &lis3lv02d_attribute_group); + return sysfs_create_group(&lis3_dev.pdev->dev.kobj, &lis3lv02d_attribute_group); } int lis3lv02d_remove_fs(void) { - sysfs_remove_group(&adev.pdev->dev.kobj, &lis3lv02d_attribute_group); - platform_device_unregister(adev.pdev); + sysfs_remove_group(&lis3_dev.pdev->dev.kobj, &lis3lv02d_attribute_group); + platform_device_unregister(lis3_dev.pdev); return 0; } EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs); +/* + * Initialise the accelerometer and the various subsystems. + * Should be rather independant of the bus system. + */ +int lis3lv02d_init_device(struct lis3lv02d *dev) +{ + dev->whoami = lis3lv02d_read_8(dev, WHO_AM_I); + + switch (dev->whoami) { + case LIS_DOUBLE_ID: + printk(KERN_INFO DRIVER_NAME ": 2-byte sensor found\n"); + dev->read_data = lis3lv02d_read_16; + dev->mdps_max_val = 2048; + break; + case LIS_SINGLE_ID: + printk(KERN_INFO DRIVER_NAME ": 1-byte sensor found\n"); + dev->read_data = lis3lv02d_read_8; + dev->mdps_max_val = 128; + break; + default: + printk(KERN_ERR DRIVER_NAME + ": unknown sensor type 0x%X\n", lis3_dev.whoami); + return -EINVAL; + } + + mutex_init(&dev->lock); + lis3lv02d_add_fs(dev); + lis3lv02d_increase_use(dev); + + if (lis3lv02d_joystick_enable()) + printk(KERN_ERR DRIVER_NAME ": joystick initialization failed\n"); + + printk("lis3_init_device: irq %d\n", dev->irq); + + /* bail if we did not get an IRQ from the bus layer */ + if (!dev->irq) { + printk(KERN_ERR DRIVER_NAME + ": No IRQ. Disabling /dev/freefall\n"); + goto out; + } + + printk("lis3: registering device\n"); + if (misc_register(&lis3lv02d_misc_device)) + printk(KERN_ERR DRIVER_NAME ": misc_register failed\n"); +out: + lis3lv02d_decrease_use(dev); + return 0; +} +EXPORT_SYMBOL_GPL(lis3lv02d_init_device); + MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver"); MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lis3lv02d.h b/drivers/hwmon/lis3lv02d.h index 75972bf372ff..745ec96806d4 100644 --- a/drivers/hwmon/lis3lv02d.h +++ b/drivers/hwmon/lis3lv02d.h @@ -159,14 +159,14 @@ struct axis_conversion { s8 z; }; -struct acpi_lis3lv02d { - struct acpi_device *device; /* The ACPI device */ - acpi_status (*init) (acpi_handle handle); - acpi_status (*write) (acpi_handle handle, int reg, u8 val); - acpi_status (*read) (acpi_handle handle, int reg, u8 *ret); +struct lis3lv02d { + void *bus_priv; /* used by the bus layer only */ + int (*init) (struct lis3lv02d *lis3); + int (*write) (struct lis3lv02d *lis3, int reg, u8 val); + int (*read) (struct lis3lv02d *lis3, int reg, u8 *ret); u8 whoami; /* 3Ah: 2-byte registries, 3Bh: 1-byte registries */ - s16 (*read_data) (acpi_handle handle, int reg); + s16 (*read_data) (struct lis3lv02d *lis3, int reg); int mdps_max_val; struct input_dev *idev; /* input device */ @@ -187,11 +187,11 @@ struct acpi_lis3lv02d { unsigned long misc_opened; /* bit0: whether the device is open */ }; -int lis3lv02d_init_device(struct acpi_lis3lv02d *dev); +int lis3lv02d_init_device(struct lis3lv02d *lis3); int lis3lv02d_joystick_enable(void); void lis3lv02d_joystick_disable(void); -void lis3lv02d_poweroff(acpi_handle handle); -void lis3lv02d_poweron(acpi_handle handle); +void lis3lv02d_poweroff(struct lis3lv02d *lis3); +void lis3lv02d_poweron(struct lis3lv02d *lis3); int lis3lv02d_remove_fs(void); -extern struct acpi_lis3lv02d adev; +extern struct lis3lv02d lis3_dev; diff --git a/drivers/hwmon/lis3lv02d_spi.c b/drivers/hwmon/lis3lv02d_spi.c new file mode 100644 index 000000000000..07ae74b0e191 --- /dev/null +++ b/drivers/hwmon/lis3lv02d_spi.c @@ -0,0 +1,114 @@ +/* + * lis3lv02d_spi - SPI glue layer for lis3lv02d + * + * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> + * + * 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 + * publishhed by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/spi/spi.h> + +#include "lis3lv02d.h" + +#define DRV_NAME "lis3lv02d_spi" +#define LIS3_SPI_READ 0x80 + +static int lis3_spi_read(struct lis3lv02d *lis3, int reg, u8 *v) +{ + struct spi_device *spi = lis3->bus_priv; + int ret = spi_w8r8(spi, reg | LIS3_SPI_READ); + if (ret < 0) + return -EINVAL; + + *v = (u8) ret; + return 0; +} + +static int lis3_spi_write(struct lis3lv02d *lis3, int reg, u8 val) +{ + u8 tmp[2] = { reg, val }; + struct spi_device *spi = lis3->bus_priv; + return spi_write(spi, tmp, sizeof(tmp)); +} + +static int lis3_spi_init(struct lis3lv02d *lis3) +{ + u8 reg; + int ret; + + /* power up the device */ + ret = lis3->read(lis3, CTRL_REG1, ®); + if (ret < 0) + return ret; + + reg |= CTRL1_PD0; + return lis3->write(lis3, CTRL_REG1, reg); +} + +static struct axis_conversion lis3lv02d_axis_normal = { 1, 2, 3 }; + +static int __devinit lis302dl_spi_probe(struct spi_device *spi) +{ + int ret; + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + ret = spi_setup(spi); + if (ret < 0) + return ret; + + lis3_dev.bus_priv = spi; + lis3_dev.init = lis3_spi_init; + lis3_dev.read = lis3_spi_read; + lis3_dev.write = lis3_spi_write; + lis3_dev.irq = spi->irq; + lis3_dev.ac = lis3lv02d_axis_normal; + spi_set_drvdata(spi, &lis3_dev); + + ret = lis3lv02d_init_device(&lis3_dev); + return ret; +} + +static int __devexit lis302dl_spi_remove(struct spi_device *spi) +{ + struct lis3lv02d *lis3 = spi_get_drvdata(spi); + lis3lv02d_joystick_disable(); + lis3lv02d_poweroff(lis3); + return 0; +} + +static struct spi_driver lis302dl_spi_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = lis302dl_spi_probe, + .remove = __devexit_p(lis302dl_spi_remove), +}; + +static int __init lis302dl_init(void) +{ + return spi_register_driver(&lis302dl_spi_driver); +} + +static void __exit lis302dl_exit(void) +{ + spi_unregister_driver(&lis302dl_spi_driver); +} + +module_init(lis302dl_init); +module_exit(lis302dl_exit); + +MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>"); +MODULE_DESCRIPTION("lis3lv02d SPI glue layer"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/hwmon/lm95241.c b/drivers/hwmon/lm95241.c new file mode 100644 index 000000000000..091d95f38aaa --- /dev/null +++ b/drivers/hwmon/lm95241.c @@ -0,0 +1,527 @@ +/* + * lm95241.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2008 Davide Rizzo <elpa-rizzo@gmail.com> + * + * Based on the max1619 driver. The LM95241 is a sensor chip made by National + * Semiconductors. + * It reports up to three temperatures (its own plus up to + * two external ones). Complete datasheet can be + * obtained from National's website at: + * http://www.national.com/ds.cgi/LM/LM95241.pdf + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/sysfs.h> + +static const unsigned short normal_i2c[] = { + 0x19, 0x2a, 0x2b, I2C_CLIENT_END}; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(lm95241); + +/* LM95241 registers */ +#define LM95241_REG_R_MAN_ID 0xFE +#define LM95241_REG_R_CHIP_ID 0xFF +#define LM95241_REG_R_STATUS 0x02 +#define LM95241_REG_RW_CONFIG 0x03 +#define LM95241_REG_RW_REM_FILTER 0x06 +#define LM95241_REG_RW_TRUTHERM 0x07 +#define LM95241_REG_W_ONE_SHOT 0x0F +#define LM95241_REG_R_LOCAL_TEMPH 0x10 +#define LM95241_REG_R_REMOTE1_TEMPH 0x11 +#define LM95241_REG_R_REMOTE2_TEMPH 0x12 +#define LM95241_REG_R_LOCAL_TEMPL 0x20 +#define LM95241_REG_R_REMOTE1_TEMPL 0x21 +#define LM95241_REG_R_REMOTE2_TEMPL 0x22 +#define LM95241_REG_RW_REMOTE_MODEL 0x30 + +/* LM95241 specific bitfields */ +#define CFG_STOP 0x40 +#define CFG_CR0076 0x00 +#define CFG_CR0182 0x10 +#define CFG_CR1000 0x20 +#define CFG_CR2700 0x30 +#define R1MS_SHIFT 0 +#define R2MS_SHIFT 2 +#define R1MS_MASK (0x01 << (R1MS_SHIFT)) +#define R2MS_MASK (0x01 << (R2MS_SHIFT)) +#define R1DF_SHIFT 1 +#define R2DF_SHIFT 2 +#define R1DF_MASK (0x01 << (R1DF_SHIFT)) +#define R2DF_MASK (0x01 << (R2DF_SHIFT)) +#define R1FE_MASK 0x01 +#define R2FE_MASK 0x05 +#define TT1_SHIFT 0 +#define TT2_SHIFT 4 +#define TT_OFF 0 +#define TT_ON 1 +#define TT_MASK 7 +#define MANUFACTURER_ID 0x01 +#define DEFAULT_REVISION 0xA4 + +/* Conversions and various macros */ +#define TEMP_FROM_REG(val_h, val_l) (((val_h) & 0x80 ? (val_h) - 0x100 : \ + (val_h)) * 1000 + (val_l) * 1000 / 256) + +/* Functions declaration */ +static int lm95241_attach_adapter(struct i2c_adapter *adapter); +static int lm95241_detect(struct i2c_adapter *adapter, int address, + int kind); +static void lm95241_init_client(struct i2c_client *client); +static int lm95241_detach_client(struct i2c_client *client); +static struct lm95241_data *lm95241_update_device(struct device *dev); + +/* Driver data (common to all clients) */ +static struct i2c_driver lm95241_driver = { + .driver = { + .name = "lm95241", + }, + .attach_adapter = lm95241_attach_adapter, + .detach_client = lm95241_detach_client, +}; + +/* Client data (each client gets its own) */ +struct lm95241_data { + struct i2c_client client; + struct device *hwmon_dev; + struct mutex update_lock; + unsigned long last_updated, rate; /* in jiffies */ + char valid; /* zero until following fields are valid */ + /* registers values */ + u8 local_h, local_l; /* local */ + u8 remote1_h, remote1_l; /* remote1 */ + u8 remote2_h, remote2_l; /* remote2 */ + u8 config, model, trutherm; +}; + +/* Sysfs stuff */ +#define show_temp(value) \ +static ssize_t show_##value(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct lm95241_data *data = lm95241_update_device(dev); \ + snprintf(buf, PAGE_SIZE - 1, "%d\n", \ + TEMP_FROM_REG(data->value##_h, data->value##_l)); \ + return strlen(buf); \ +} +show_temp(local); +show_temp(remote1); +show_temp(remote2); + +static ssize_t show_rate(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm95241_data *data = lm95241_update_device(dev); + + snprintf(buf, PAGE_SIZE - 1, "%lu\n", 1000 * data->rate / HZ); + return strlen(buf); +} + +static ssize_t set_rate(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95241_data *data = i2c_get_clientdata(client); + + strict_strtol(buf, 10, &data->rate); + data->rate = data->rate * HZ / 1000; + + return count; +} + +#define show_type(flag) \ +static ssize_t show_type##flag(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct lm95241_data *data = i2c_get_clientdata(client); \ +\ + snprintf(buf, PAGE_SIZE - 1, \ + data->model & R##flag##MS_MASK ? "1\n" : "2\n"); \ + return strlen(buf); \ +} +show_type(1); +show_type(2); + +#define show_min(flag) \ +static ssize_t show_min##flag(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct lm95241_data *data = i2c_get_clientdata(client); \ +\ + snprintf(buf, PAGE_SIZE - 1, \ + data->config & R##flag##DF_MASK ? \ + "-127000\n" : "0\n"); \ + return strlen(buf); \ +} +show_min(1); +show_min(2); + +#define show_max(flag) \ +static ssize_t show_max##flag(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct lm95241_data *data = i2c_get_clientdata(client); \ +\ + snprintf(buf, PAGE_SIZE - 1, \ + data->config & R##flag##DF_MASK ? \ + "127000\n" : "255000\n"); \ + return strlen(buf); \ +} +show_max(1); +show_max(2); + +#define set_type(flag) \ +static ssize_t set_type##flag(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct lm95241_data *data = i2c_get_clientdata(client); \ +\ + long val; \ + strict_strtol(buf, 10, &val); \ +\ + if ((val == 1) || (val == 2)) { \ +\ + mutex_lock(&data->update_lock); \ +\ + data->trutherm &= ~(TT_MASK << TT##flag##_SHIFT); \ + if (val == 1) { \ + data->model |= R##flag##MS_MASK; \ + data->trutherm |= (TT_ON << TT##flag##_SHIFT); \ + } \ + else { \ + data->model &= ~R##flag##MS_MASK; \ + data->trutherm |= (TT_OFF << TT##flag##_SHIFT); \ + } \ +\ + data->valid = 0; \ +\ + i2c_smbus_write_byte_data(client, LM95241_REG_RW_REMOTE_MODEL, \ + data->model); \ + i2c_smbus_write_byte_data(client, LM95241_REG_RW_TRUTHERM, \ + data->trutherm); \ +\ + mutex_unlock(&data->update_lock); \ +\ + } \ + return count; \ +} +set_type(1); +set_type(2); + +#define set_min(flag) \ +static ssize_t set_min##flag(struct device *dev, \ + struct device_attribute *devattr, const char *buf, size_t count) \ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct lm95241_data *data = i2c_get_clientdata(client); \ +\ + long val; \ + strict_strtol(buf, 10, &val); \ +\ + mutex_lock(&data->update_lock); \ +\ + if (val < 0) \ + data->config |= R##flag##DF_MASK; \ + else \ + data->config &= ~R##flag##DF_MASK; \ +\ + data->valid = 0; \ +\ + i2c_smbus_write_byte_data(client, LM95241_REG_RW_CONFIG, \ + data->config); \ +\ + mutex_unlock(&data->update_lock); \ +\ + return count; \ +} +set_min(1); +set_min(2); + +#define set_max(flag) \ +static ssize_t set_max##flag(struct device *dev, \ + struct device_attribute *devattr, const char *buf, size_t count) \ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct lm95241_data *data = i2c_get_clientdata(client); \ +\ + long val; \ + strict_strtol(buf, 10, &val); \ +\ + mutex_lock(&data->update_lock); \ +\ + if (val <= 127000) \ + data->config |= R##flag##DF_MASK; \ + else \ + data->config &= ~R##flag##DF_MASK; \ +\ + data->valid = 0; \ +\ + i2c_smbus_write_byte_data(client, LM95241_REG_RW_CONFIG, \ + data->config); \ +\ + mutex_unlock(&data->update_lock); \ +\ + return count; \ +} +set_max(1); +set_max(2); + +static DEVICE_ATTR(temp1_input, S_IRUGO, show_local, NULL); +static DEVICE_ATTR(temp2_input, S_IRUGO, show_remote1, NULL); +static DEVICE_ATTR(temp3_input, S_IRUGO, show_remote2, NULL); +static DEVICE_ATTR(temp2_type, S_IWUSR | S_IRUGO, show_type1, set_type1); +static DEVICE_ATTR(temp3_type, S_IWUSR | S_IRUGO, show_type2, set_type2); +static DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min1, set_min1); +static DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_min2, set_min2); +static DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max1, set_max1); +static DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_max2, set_max2); +static DEVICE_ATTR(rate, S_IWUSR | S_IRUGO, show_rate, set_rate); + +static struct attribute *lm95241_attributes[] = { + &dev_attr_temp1_input.attr, + &dev_attr_temp2_input.attr, + &dev_attr_temp3_input.attr, + &dev_attr_temp2_type.attr, + &dev_attr_temp3_type.attr, + &dev_attr_temp2_min.attr, + &dev_attr_temp3_min.attr, + &dev_attr_temp2_max.attr, + &dev_attr_temp3_max.attr, + &dev_attr_rate.attr, + NULL +}; + +static const struct attribute_group lm95241_group = { + .attrs = lm95241_attributes, +}; + +/* Init/exit code */ +static int lm95241_attach_adapter(struct i2c_adapter *adapter) +{ + if (!(adapter->class & I2C_CLASS_HWMON)) + return 0; + return i2c_probe(adapter, &addr_data, lm95241_detect); +} + +/* + * The following function does more than just detection. If detection + * succeeds, it also registers the new chip. + */ +static int lm95241_detect(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *new_client; + struct lm95241_data *data; + int err = 0; + const char *name = ""; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto exit; + + data = kzalloc(sizeof(struct lm95241_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + /* The common I2C client data is placed right before the + LM95241-specific data. */ + new_client = &data->client; + i2c_set_clientdata(new_client, data); + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &lm95241_driver; + new_client->flags = 0; + + /* + * Now we do the remaining detection. A negative kind means that + * the driver was loaded with no force parameter (default), so we + * must both detect and identify the chip. A zero kind means that + * the driver was loaded with the force parameter, the detection + * step shall be skipped. A positive kind means that the driver + * was loaded with the force parameter and a given kind of chip is + * requested, so both the detection and the identification steps + * are skipped. + */ + if (kind < 0) { /* detection */ + if ((i2c_smbus_read_byte_data(new_client, LM95241_REG_R_MAN_ID) + != MANUFACTURER_ID) + || (i2c_smbus_read_byte_data(new_client, LM95241_REG_R_CHIP_ID) + < DEFAULT_REVISION)) { + dev_dbg(&adapter->dev, + "LM95241 detection failed at 0x%02x.\n", + address); + goto exit_free; + } + } + + if (kind <= 0) { /* identification */ + if ((i2c_smbus_read_byte_data(new_client, LM95241_REG_R_MAN_ID) + == MANUFACTURER_ID) + && (i2c_smbus_read_byte_data(new_client, LM95241_REG_R_CHIP_ID) + >= DEFAULT_REVISION)) { + + kind = lm95241; + + if (kind <= 0) { /* identification failed */ + dev_info(&adapter->dev, "Unsupported chip\n"); + goto exit_free; + } + } + } + + if (kind == lm95241) + name = "lm95241"; + + /* We can fill in the remaining client fields */ + strlcpy(new_client->name, name, I2C_NAME_SIZE); + data->valid = 0; + mutex_init(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + err = i2c_attach_client(new_client); + if (err) + goto exit_free; + + /* Initialize the LM95241 chip */ + lm95241_init_client(new_client); + + /* Register sysfs hooks */ + err = sysfs_create_group(&new_client->dev.kobj, &lm95241_group); + if (err) + goto exit_detach; + + data->hwmon_dev = hwmon_device_register(&new_client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: + sysfs_remove_group(&new_client->dev.kobj, &lm95241_group); +exit_detach: + i2c_detach_client(new_client); +exit_free: + kfree(data); +exit: + return err; +} + +static void lm95241_init_client(struct i2c_client *client) +{ + struct lm95241_data *data = i2c_get_clientdata(client); + + data->rate = HZ; /* 1 sec default */ + data->valid = 0; + data->config = CFG_CR0076; + data->model = 0; + data->trutherm = (TT_OFF << TT1_SHIFT) | (TT_OFF << TT2_SHIFT); + + i2c_smbus_write_byte_data(client, LM95241_REG_RW_CONFIG, + data->config); + i2c_smbus_write_byte_data(client, LM95241_REG_RW_REM_FILTER, + R1FE_MASK | R2FE_MASK); + i2c_smbus_write_byte_data(client, LM95241_REG_RW_TRUTHERM, + data->trutherm); + i2c_smbus_write_byte_data(client, LM95241_REG_RW_REMOTE_MODEL, + data->model); +} + +static int lm95241_detach_client(struct i2c_client *client) +{ + struct lm95241_data *data = i2c_get_clientdata(client); + int err; + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &lm95241_group); + + err = i2c_detach_client(client); + if (err) + return err; + + kfree(data); + return 0; +} + +static struct lm95241_data *lm95241_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm95241_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + data->rate) || + !data->valid) { + dev_dbg(&client->dev, "Updating lm95241 data.\n"); + data->local_h = + i2c_smbus_read_byte_data(client, + LM95241_REG_R_LOCAL_TEMPH); + data->local_l = + i2c_smbus_read_byte_data(client, + LM95241_REG_R_LOCAL_TEMPL); + data->remote1_h = + i2c_smbus_read_byte_data(client, + LM95241_REG_R_REMOTE1_TEMPH); + data->remote1_l = + i2c_smbus_read_byte_data(client, + LM95241_REG_R_REMOTE1_TEMPL); + data->remote2_h = + i2c_smbus_read_byte_data(client, + LM95241_REG_R_REMOTE2_TEMPH); + data->remote2_l = + i2c_smbus_read_byte_data(client, + LM95241_REG_R_REMOTE2_TEMPL); + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static int __init sensors_lm95241_init(void) +{ + return i2c_add_driver(&lm95241_driver); +} + +static void __exit sensors_lm95241_exit(void) +{ + i2c_del_driver(&lm95241_driver); +} + +MODULE_AUTHOR("Davide Rizzo <elpa-rizzo@gmail.com>"); +MODULE_DESCRIPTION("LM95241 sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_lm95241_init); +module_exit(sensors_lm95241_exit); diff --git a/drivers/hwmon/ltc4215.c b/drivers/hwmon/ltc4215.c new file mode 100644 index 000000000000..9386e2a39211 --- /dev/null +++ b/drivers/hwmon/ltc4215.c @@ -0,0 +1,364 @@ +/* + * Driver for Linear Technology LTC4215 I2C Hot Swap Controller + * + * Copyright (C) 2009 Ira W. Snyder <iws@ovro.caltech.edu> + * + * 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. + * + * Datasheet: + * http://www.linear.com/pc/downloadDocument.do?navId=H0,C1,C1003,C1006,C1163,P17572,D12697 + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> + +static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(ltc4215); + +/* Here are names of the chip's registers (a.k.a. commands) */ +enum ltc4215_cmd { + LTC4215_CONTROL = 0x00, /* rw */ + LTC4215_ALERT = 0x01, /* rw */ + LTC4215_STATUS = 0x02, /* ro */ + LTC4215_FAULT = 0x03, /* rw */ + LTC4215_SENSE = 0x04, /* rw */ + LTC4215_SOURCE = 0x05, /* rw */ + LTC4215_ADIN = 0x06, /* rw */ +}; + +struct ltc4215_data { + struct device *hwmon_dev; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + /* Registers */ + u8 regs[7]; +}; + +static struct ltc4215_data *ltc4215_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ltc4215_data *data = i2c_get_clientdata(client); + s32 val; + int i; + + mutex_lock(&data->update_lock); + + /* The chip's A/D updates 10 times per second */ + if (time_after(jiffies, data->last_updated + HZ / 10) || !data->valid) { + + dev_dbg(&client->dev, "Starting ltc4215 update\n"); + + /* Read all registers */ + for (i = 0; i < ARRAY_SIZE(data->regs); i++) { + val = i2c_smbus_read_byte_data(client, i); + if (unlikely(val < 0)) + data->regs[i] = 0; + else + data->regs[i] = val; + } + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* Return the voltage from the given register in millivolts */ +static int ltc4215_get_voltage(struct device *dev, u8 reg) +{ + struct ltc4215_data *data = ltc4215_update_device(dev); + const u8 regval = data->regs[reg]; + u32 voltage = 0; + + switch (reg) { + case LTC4215_SENSE: + /* 151 uV per increment */ + voltage = regval * 151 / 1000; + break; + case LTC4215_SOURCE: + /* 60.5 mV per increment */ + voltage = regval * 605 / 10; + break; + case LTC4215_ADIN: + /* The ADIN input is divided by 12.5, and has 4.82 mV + * per increment, so we have the additional multiply */ + voltage = regval * 482 * 125 / 1000; + break; + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + break; + } + + return voltage; +} + +/* Return the current from the sense resistor in mA */ +static unsigned int ltc4215_get_current(struct device *dev) +{ + struct ltc4215_data *data = ltc4215_update_device(dev); + + /* The strange looking conversions that follow are fixed-point + * math, since we cannot do floating point in the kernel. + * + * Step 1: convert sense register to microVolts + * Step 2: convert voltage to milliAmperes + * + * If you play around with the V=IR equation, you come up with + * the following: X uV / Y mOhm == Z mA + * + * With the resistors that are fractions of a milliOhm, we multiply + * the voltage and resistance by 10, to shift the decimal point. + * Now we can use the normal division operator again. + */ + + /* Calculate voltage in microVolts (151 uV per increment) */ + const unsigned int voltage = data->regs[LTC4215_SENSE] * 151; + + /* Calculate current in milliAmperes (4 milliOhm sense resistor) */ + const unsigned int curr = voltage / 4; + + return curr; +} + +static ssize_t ltc4215_show_voltage(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + const int voltage = ltc4215_get_voltage(dev, attr->index); + + return snprintf(buf, PAGE_SIZE, "%d\n", voltage); +} + +static ssize_t ltc4215_show_current(struct device *dev, + struct device_attribute *da, + char *buf) +{ + const unsigned int curr = ltc4215_get_current(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", curr); +} + +static ssize_t ltc4215_show_power(struct device *dev, + struct device_attribute *da, + char *buf) +{ + const unsigned int curr = ltc4215_get_current(dev); + const int output_voltage = ltc4215_get_voltage(dev, LTC4215_ADIN); + + /* current in mA * voltage in mV == power in uW */ + const unsigned int power = abs(output_voltage * curr); + + return snprintf(buf, PAGE_SIZE, "%u\n", power); +} + +static ssize_t ltc4215_show_alarm(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da); + struct ltc4215_data *data = ltc4215_update_device(dev); + const u8 reg = data->regs[attr->index]; + const u32 mask = attr->nr; + + return snprintf(buf, PAGE_SIZE, "%u\n", (reg & mask) ? 1 : 0); +} + +/* These macros are used below in constructing device attribute objects + * for use with sysfs_create_group() to make a sysfs device file + * for each register. + */ + +#define LTC4215_VOLTAGE(name, ltc4215_cmd_idx) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4215_show_voltage, NULL, ltc4215_cmd_idx) + +#define LTC4215_CURRENT(name) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4215_show_current, NULL, 0); + +#define LTC4215_POWER(name) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4215_show_power, NULL, 0); + +#define LTC4215_ALARM(name, mask, reg) \ + static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, \ + ltc4215_show_alarm, NULL, (mask), reg) + +/* Construct a sensor_device_attribute structure for each register */ + +/* Current */ +LTC4215_CURRENT(curr1_input); +LTC4215_ALARM(curr1_max_alarm, (1 << 2), LTC4215_STATUS); + +/* Power (virtual) */ +LTC4215_POWER(power1_input); +LTC4215_ALARM(power1_alarm, (1 << 3), LTC4215_STATUS); + +/* Input Voltage */ +LTC4215_VOLTAGE(in1_input, LTC4215_ADIN); +LTC4215_ALARM(in1_max_alarm, (1 << 0), LTC4215_STATUS); +LTC4215_ALARM(in1_min_alarm, (1 << 1), LTC4215_STATUS); + +/* Output Voltage */ +LTC4215_VOLTAGE(in2_input, LTC4215_SOURCE); + +/* Finally, construct an array of pointers to members of the above objects, + * as required for sysfs_create_group() + */ +static struct attribute *ltc4215_attributes[] = { + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, + + &sensor_dev_attr_power1_input.dev_attr.attr, + &sensor_dev_attr_power1_alarm.dev_attr.attr, + + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + + &sensor_dev_attr_in2_input.dev_attr.attr, + + NULL, +}; + +static const struct attribute_group ltc4215_group = { + .attrs = ltc4215_attributes, +}; + +static int ltc4215_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ltc4215_data *data; + int ret; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto out_kzalloc; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Initialize the LTC4215 chip */ + /* TODO */ + + /* Register sysfs hooks */ + ret = sysfs_create_group(&client->dev.kobj, <c4215_group); + if (ret) + goto out_sysfs_create_group; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto out_hwmon_device_register; + } + + return 0; + +out_hwmon_device_register: + sysfs_remove_group(&client->dev.kobj, <c4215_group); +out_sysfs_create_group: + kfree(data); +out_kzalloc: + return ret; +} + +static int ltc4215_remove(struct i2c_client *client) +{ + struct ltc4215_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, <c4215_group); + + kfree(data); + + return 0; +} + +static int ltc4215_detect(struct i2c_client *client, + int kind, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + if (kind < 0) { /* probed detection - check the chip type */ + s32 v; /* 8 bits from the chip, or -ERRNO */ + + /* + * Register 0x01 bit b7 is reserved, expect 0 + * Register 0x03 bit b6 and b7 are reserved, expect 0 + */ + v = i2c_smbus_read_byte_data(client, LTC4215_ALERT); + if (v < 0 || (v & (1 << 7)) != 0) + return -ENODEV; + + v = i2c_smbus_read_byte_data(client, LTC4215_FAULT); + if (v < 0 || (v & ((1 << 6) | (1 << 7))) != 0) + return -ENODEV; + } + + strlcpy(info->type, "ltc4215", I2C_NAME_SIZE); + dev_info(&adapter->dev, "ltc4215 %s at address 0x%02x\n", + kind < 0 ? "probed" : "forced", + client->addr); + + return 0; +} + +static const struct i2c_device_id ltc4215_id[] = { + { "ltc4215", ltc4215 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc4215_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver ltc4215_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "ltc4215", + }, + .probe = ltc4215_probe, + .remove = ltc4215_remove, + .id_table = ltc4215_id, + .detect = ltc4215_detect, + .address_data = &addr_data, +}; + +static int __init ltc4215_init(void) +{ + return i2c_add_driver(<c4215_driver); +} + +static void __exit ltc4215_exit(void) +{ + i2c_del_driver(<c4215_driver); +} + +MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>"); +MODULE_DESCRIPTION("LTC4215 driver"); +MODULE_LICENSE("GPL"); + +module_init(ltc4215_init); +module_exit(ltc4215_exit); |