diff options
author | Zhou Jingyu <b02241@freescale.com> | 2011-03-02 15:36:28 +0800 |
---|---|---|
committer | Jason Liu <r64343@freescale.com> | 2012-01-09 19:53:51 +0800 |
commit | 3b91b5ac920ba4e716afb8618ee10d6fb7711852 (patch) | |
tree | 75f4dbe0c38918b549c2f048bfa4ea02733586a4 /drivers | |
parent | 28a8e184e019fa7b520521dd26e966d7f02302fc (diff) |
ENGR00141391-1 PMIC: Add DA9053 source code from Dialog
Add DA9053 original source code
Signed-off-by: Zhou Jingyu <Jingyu.Zhou@freescale.com>
Acked-by: Lily Zhang <r58066@freescale.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/gpio/da9052-gpio.c | 731 | ||||
-rw-r--r-- | drivers/hwmon/da9052-adc.c | 644 | ||||
-rw-r--r-- | drivers/input/misc/da9052_onkey.c | 133 | ||||
-rw-r--r-- | drivers/input/touchscreen/da9052_tsi.c | 1448 | ||||
-rw-r--r-- | drivers/input/touchscreen/da9052_tsi_calibrate.c | 107 | ||||
-rw-r--r-- | drivers/input/touchscreen/da9052_tsi_filter.c | 489 | ||||
-rw-r--r-- | drivers/leds/leds-da9052.c | 307 | ||||
-rw-r--r-- | drivers/mfd/da9052-core.c | 533 | ||||
-rw-r--r-- | drivers/mfd/da9052-i2c.c | 376 | ||||
-rw-r--r-- | drivers/mfd/da9052-spi.c | 399 | ||||
-rw-r--r-- | drivers/power/da9052-battery.c | 844 | ||||
-rw-r--r-- | drivers/regulator/da9052-regulator.c | 487 | ||||
-rw-r--r-- | drivers/rtc/rtc-da9052.c | 694 | ||||
-rw-r--r-- | drivers/spi/spi_sam.c | 1161 | ||||
-rw-r--r-- | drivers/video/backlight/da9052_bl.c | 455 | ||||
-rw-r--r-- | drivers/watchdog/da9052_wdt.c | 542 |
16 files changed, 9350 insertions, 0 deletions
diff --git a/drivers/gpio/da9052-gpio.c b/drivers/gpio/da9052-gpio.c new file mode 100644 index 000000000000..a9c53f219c3e --- /dev/null +++ b/drivers/gpio/da9052-gpio.c @@ -0,0 +1,731 @@ +/* + * da9052-gpio.c -- GPIO Driver for Dialog DA9052 + * + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * Author: Dialog Semiconductor Ltd <dchen@diasemi.com> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> +#include <linux/syscalls.h> +#include <linux/seq_file.h> +#include <linux/gpio.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/gpio.h> + +#define DRIVER_NAME "da9052-gpio" +static inline struct da9052_gpio_chip *to_da9052_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct da9052_gpio_chip, gp); +} + +void da9052_gpio_notifier(struct da9052_eh_nb *eh_data, unsigned int event) +{ + struct da9052_gpio_chip *gpio = + container_of(eh_data, struct da9052_gpio_chip, eh_data); + kobject_uevent(&gpio->gp.dev->kobj, KOBJ_CHANGE); + printk(KERN_INFO "Event received from GPIO8\n"); +} + +static u8 create_gpio_config_value(u8 gpio_function, u8 gpio_type, u8 gpio_mode) +{ + /* The format is - + function - 2 bits + type - 1 bit + mode - 1 bit */ + return gpio_function | (gpio_type << 2) | (gpio_mode << 3); +} + +static s32 write_default_gpio_values(struct da9052 *da9052) +{ + struct da9052_ssc_msg msg; + u8 created_val = 0; + +#if (DA9052_GPIO_PIN_0 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO0001_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO0_FUNCTION, + DEFAULT_GPIO0_TYPE, DEFAULT_GPIO0_MODE); + msg.data &= DA9052_GPIO_MASK_UPPER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +#if (DA9052_GPIO_PIN_1 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO0001_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO1_FUNCTION, + DEFAULT_GPIO1_TYPE, DEFAULT_GPIO1_MODE); + created_val = created_val << DA9052_GPIO_NIBBLE_SHIFT; + msg.data &= DA9052_GPIO_MASK_LOWER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +/* GPIO 2-3*/ +#if (DA9052_GPIO_PIN_2 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO0203_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO2_FUNCTION, + DEFAULT_GPIO2_TYPE, DEFAULT_GPIO2_MODE); + msg.data &= DA9052_GPIO_MASK_UPPER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +#if (DA9052_GPIO_PIN_3 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO0203_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO3_FUNCTION, + DEFAULT_GPIO3_TYPE, DEFAULT_GPIO3_MODE); + created_val = created_val << DA9052_GPIO_NIBBLE_SHIFT; + msg.data &= DA9052_GPIO_MASK_LOWER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +/* GPIO 4-5*/ +#if (DA9052_GPIO_PIN_4 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO0405_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO4_FUNCTION, + DEFAULT_GPIO4_TYPE, DEFAULT_GPIO4_MODE); + msg.data &= DA9052_GPIO_MASK_UPPER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +#if (DA9052_GPIO_PIN_5 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO0405_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO5_FUNCTION, + DEFAULT_GPIO5_TYPE, DEFAULT_GPIO5_MODE); + created_val = created_val << DA9052_GPIO_NIBBLE_SHIFT; + msg.data &= DA9052_GPIO_MASK_LOWER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +/* GPIO 6-7*/ +#if (DA9052_GPIO_PIN_6 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO0607_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO6_FUNCTION, + DEFAULT_GPIO6_TYPE, DEFAULT_GPIO6_MODE); + msg.data &= DA9052_GPIO_MASK_UPPER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +#if (DA9052_GPIO_PIN_7 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO0607_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO7_FUNCTION, + DEFAULT_GPIO7_TYPE, DEFAULT_GPIO7_MODE); + created_val = created_val << DA9052_GPIO_NIBBLE_SHIFT; + msg.data &= DA9052_GPIO_MASK_LOWER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +/* GPIO 8-9*/ +#if (DA9052_GPIO_PIN_8 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO0809_REG; + msg.data = 0; + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO8_FUNCTION, + DEFAULT_GPIO8_TYPE, DEFAULT_GPIO8_MODE); + msg.data &= DA9052_GPIO_MASK_UPPER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +#if (DA9052_GPIO_PIN_9 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO0809_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO9_FUNCTION, + DEFAULT_GPIO9_TYPE, DEFAULT_GPIO9_MODE); + created_val = created_val << DA9052_GPIO_NIBBLE_SHIFT; + msg.data &= DA9052_GPIO_MASK_LOWER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +/* GPIO 10-11*/ +#if (DA9052_GPIO_PIN_10 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO1011_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO10_FUNCTION, + DEFAULT_GPIO10_TYPE, DEFAULT_GPIO10_MODE); + msg.data &= DA9052_GPIO_MASK_UPPER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +#if (DA9052_GPIO_PIN_11 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO1011_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO11_FUNCTION, + DEFAULT_GPIO11_TYPE, DEFAULT_GPIO11_MODE); + created_val = created_val << DA9052_GPIO_NIBBLE_SHIFT; + msg.data &= DA9052_GPIO_MASK_LOWER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +/* GPIO 12-13*/ +#if (DA9052_GPIO_PIN_12 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO1213_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO12_FUNCTION, + DEFAULT_GPIO12_TYPE, DEFAULT_GPIO12_MODE); + msg.data &= DA9052_GPIO_MASK_UPPER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +#if (DA9052_GPIO_PIN_13 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO1213_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO13_FUNCTION, + DEFAULT_GPIO13_TYPE, DEFAULT_GPIO13_MODE); + created_val = created_val << DA9052_GPIO_NIBBLE_SHIFT; + msg.data &= DA9052_GPIO_MASK_LOWER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +/* GPIO 14-15*/ +#if (DA9052_GPIO_PIN_14 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO1415_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO14_FUNCTION, + DEFAULT_GPIO14_TYPE, DEFAULT_GPIO14_MODE); + msg.data &= DA9052_GPIO_MASK_UPPER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif +#if (DA9052_GPIO_PIN_15 == DA9052_GPIO_CONFIG) + da9052_lock(da9052); + msg.addr = DA9052_GPIO1415_REG; + msg.data = 0; + + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + + created_val = create_gpio_config_value(DEFAULT_GPIO15_FUNCTION, + DEFAULT_GPIO15_TYPE, DEFAULT_GPIO15_MODE); + created_val = created_val << DA9052_GPIO_NIBBLE_SHIFT; + msg.data &= DA9052_GPIO_MASK_LOWER_NIBBLE; + msg.data |= created_val; + + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); +#endif + return 0; +} + +s32 da9052_gpio_read_port(struct da9052_gpio_read_write *read_port, + struct da9052 *da9052) +{ + struct da9052_ssc_msg msg; + u8 shift_value = 0; + u8 port_functionality = 0; + msg.addr = (read_port->port_number / 2) + DA9052_GPIO0001_REG; + msg.data = 0; + da9052_lock(da9052); + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); + port_functionality = + (read_port->port_number % 2) ? + ((msg.data & DA9052_GPIO_ODD_PORT_FUNCTIONALITY) >> + DA9052_GPIO_NIBBLE_SHIFT) : + (msg.data & DA9052_GPIO_EVEN_PORT_FUNCTIONALITY); + + if (port_functionality != INPUT) + return DA9052_GPIO_INVALID_PORTNUMBER; + + if (read_port->port_number >= (DA9052_GPIO_MAX_PORTNUMBER)) + return DA9052_GPIO_INVALID_PORTNUMBER; + + if (read_port->port_number < DA9052_GPIO_MAX_PORTS_PER_REGISTER) + msg.addr = DA9052_STATUSC_REG; + else + msg.addr = DA9052_STATUSD_REG; + msg.data = 0; + + da9052_lock(da9052); + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); + + shift_value = msg.data & + (1 << DA9052_GPIO_SHIFT_COUNT(read_port->port_number)); + read_port->read_write_value = (shift_value >> + DA9052_GPIO_SHIFT_COUNT(read_port->port_number)); + + return 0; +} + +s32 da9052_gpio_multiple_read(struct da9052_gpio_multiple_read *multiple_port, + struct da9052 *da9052) +{ + struct da9052_ssc_msg msg[2]; + u8 port_number = 0; + u8 loop_index = 0; + msg[loop_index++].addr = DA9052_STATUSC_REG; + msg[loop_index++].addr = DA9052_STATUSD_REG; + + da9052_lock(da9052); + if (da9052->read_many(da9052, msg, loop_index)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); + loop_index = 0; + for (port_number = 0; port_number < DA9052_GPIO_MAX_PORTS_PER_REGISTER; + port_number++) { + multiple_port->signal_value[port_number] = + msg[loop_index].data & 1; + msg[loop_index].data = msg[loop_index].data >> 1; + } + loop_index++; + for (port_number = DA9052_GPIO_MAX_PORTS_PER_REGISTER; + port_number < DA9052_GPIO_MAX_PORTNUMBER; port_number++) { + multiple_port->signal_value[port_number] = + msg[loop_index].data & 1; + msg[loop_index].data = msg[loop_index].data >> 1; + } + return 0; +} +EXPORT_SYMBOL(da9052_gpio_multiple_read); + +s32 da9052_gpio_write_port(struct da9052_gpio_read_write *write_port, + struct da9052 *da9052) +{ + struct da9052_ssc_msg msg; + u8 port_functionality = 0; + u8 bit_pos = 0; + msg.addr = DA9052_GPIO0001_REG + (write_port->port_number / 2); + msg.data = 0; + + da9052_lock(da9052); + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); + + port_functionality = + (write_port->port_number % 2) ? + ((msg.data & DA9052_GPIO_ODD_PORT_FUNCTIONALITY) >> + DA9052_GPIO_NIBBLE_SHIFT) : + (msg.data & DA9052_GPIO_EVEN_PORT_FUNCTIONALITY); + + if (port_functionality < 2) + return DA9052_GPIO_INVALID_PORTNUMBER; + + bit_pos = (write_port->port_number % 2) ? + DA9052_GPIO_ODD_PORT_WRITE_MODE : + DA9052_GPIO_EVEN_PORT_WRITE_MODE; + + if (write_port->read_write_value) + msg.data = msg.data | bit_pos; + else + msg.data = (msg.data & ~(bit_pos)); + + da9052_lock(da9052); + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); + return 0; +} + +s32 da9052_gpio_configure_port(struct da9052_gpio *gpio_data, + struct da9052 *da9052) +{ + struct da9052_ssc_msg msg; + u8 register_value = 0; + u8 function = 0; + u8 port_functionality = 0; + msg.addr = (gpio_data->port_number / 2) + DA9052_GPIO0001_REG; + msg.data = 0; + + da9052_lock(da9052); + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); + + port_functionality = + (gpio_data->port_number % 2) ? + ((msg.data & DA9052_GPIO_ODD_PORT_FUNCTIONALITY) >> + DA9052_GPIO_NIBBLE_SHIFT) : + (msg.data & DA9052_GPIO_EVEN_PORT_FUNCTIONALITY); + if (port_functionality < INPUT) + return DA9052_GPIO_INVALID_PORTNUMBER; + if (gpio_data->gpio_config.input.type > ACTIVE_HIGH) + return DA9052_GPIO_INVALID_TYPE; + if (gpio_data->gpio_config.input.mode > DEBOUNCING_ON) + return DA9052_GPIO_INVALID_MODE; + function = gpio_data->gpio_function; + switch (function) { + case INPUT: + register_value = create_gpio_config_value(function, + gpio_data->gpio_config.input.type, + gpio_data->gpio_config.input.mode); + break; + case OUTPUT_OPENDRAIN: + case OUTPUT_PUSHPULL: + register_value = create_gpio_config_value(function, + gpio_data->gpio_config.input.type, + gpio_data->gpio_config.input.mode); + break; + default: + return DA9052_GPIO_INVALID_FUNCTION; + break; + } + + if (gpio_data->port_number % 2) { + msg.data = (msg.data & ~(DA9052_GPIO_MASK_UPPER_NIBBLE)) | + (register_value << DA9052_GPIO_NIBBLE_SHIFT); + } else { + msg.data = (msg.data & ~(DA9052_GPIO_MASK_LOWER_NIBBLE)) | + register_value; + } + da9052_lock(da9052); + if (da9052->write(da9052, &msg)) { + da9052_unlock(da9052); + return -EIO; + } + da9052_unlock(da9052); + return 0; +} + +static s32 da9052_gpio_read(struct gpio_chip *gc, u32 offset) +{ + struct da9052_gpio_chip *gpio; + gpio = to_da9052_gpio(gc); + gpio->read_write.port_number = offset; + da9052_gpio_read_port(&gpio->read_write, gpio->da9052); + return gpio->read_write.read_write_value; +} + +static void da9052_gpio_write(struct gpio_chip *gc, u32 offset, s32 value) +{ + struct da9052_gpio_chip *gpio; + gpio = to_da9052_gpio(gc); + gpio->read_write.port_number = offset; + gpio->read_write.read_write_value = (u8)value; + da9052_gpio_write_port(&gpio->read_write, gpio->da9052); +} + +static s32 da9052_gpio_ip(struct gpio_chip *gc, u32 offset) +{ + struct da9052_gpio_chip *gpio; + gpio = to_da9052_gpio(gc); + gpio->gpio.gpio_function = INPUT; + gpio->gpio.gpio_config.input.type = ACTIVE_LOW; + gpio->gpio.gpio_config.input.mode = DEBOUNCING_ON; + gpio->gpio.port_number = offset; + return da9052_gpio_configure_port(&gpio->gpio, gpio->da9052); +} + +static s32 da9052_gpio_op(struct gpio_chip *gc, u32 offset, s32 value) +{ + struct da9052_gpio_chip *gpio; + gpio = to_da9052_gpio(gc); + gpio->gpio.gpio_function = OUTPUT_PUSHPULL; + gpio->gpio.gpio_config.output.type = SUPPLY_VDD_IO1; + gpio->gpio.gpio_config.output.mode = value; + gpio->gpio.port_number = offset; + return da9052_gpio_configure_port(&gpio->gpio, gpio->da9052); +} + +static int da9052_gpio_to_irq(struct gpio_chip *gc, u32 offset) +{ + struct da9052_gpio_chip *gpio; + gpio = to_da9052_gpio(gc); + kobject_uevent(&gpio->gp.dev->kobj, KOBJ_CHANGE); + printk(KERN_INFO"gpio->gp.base +offset = %d\n", gpio->gp.base + offset); + printk(KERN_INFO"Test1\n\n"); + return gpio->gp.base + offset; +} + +static int __devinit da9052_gpio_probe(struct platform_device *pdev) +{ + struct da9052_gpio_chip *gpio; + struct da9052_platform_data *pdata = (pdev->dev.platform_data); + s32 ret; + gpio = kzalloc(sizeof(*gpio), GFP_KERNEL); + if (gpio == NULL) + return -ENOMEM; + gpio->da9052 = dev_get_drvdata(pdev->dev.parent); + gpio->gp.get = da9052_gpio_read; + gpio->gp.direction_input = da9052_gpio_ip; + gpio->gp.direction_output = da9052_gpio_op; + gpio->gp.set = da9052_gpio_write; + + gpio->gp.base = pdata->gpio_base; + gpio->gp.ngpio = DA9052_GPIO_MAX_PORTNUMBER; + gpio->gp.can_sleep = 1; + gpio->gp.dev = &pdev->dev; + gpio->gp.owner = THIS_MODULE; + gpio->gp.label = "da9052-gpio"; + gpio->gp.to_irq = da9052_gpio_to_irq; + + gpio->eh_data.eve_type = GPI8_EVE; + gpio->eh_data.call_back = &da9052_gpio_notifier; + ret = gpio->da9052->register_event_notifier(gpio->da9052, + &gpio->eh_data); + + ret = write_default_gpio_values(gpio->da9052); + if (ret < 0) { + dev_err(&pdev->dev, "GPIO initial config failed, %d\n", + ret); + goto ret; + } + + ret = gpiochip_add(&gpio->gp); + if (ret < 0) { + dev_err(&pdev->dev, "Could not register gpiochip, %d\n", + ret); + goto ret; + } + platform_set_drvdata(pdev, gpio); + + return ret; + +ret: + kfree(gpio); + return ret; + +} + +static int __devexit da9052_gpio_remove(struct platform_device *pdev) +{ + struct da9052_gpio_chip *gpio = platform_get_drvdata(pdev); + int ret; + + gpio->da9052->unregister_event_notifier + (gpio->da9052, &gpio->eh_data); + ret = gpiochip_remove(&gpio->gp); + if (ret == 0) + kfree(gpio); + return 0; +} + +static struct platform_driver da9052_gpio_driver = { + .probe = da9052_gpio_probe, + .remove = __devexit_p(da9052_gpio_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init da9052_gpio_init(void) +{ + return platform_driver_register(&da9052_gpio_driver); +} + +static void __exit da9052_gpio_exit(void) +{ + return platform_driver_unregister(&da9052_gpio_driver); +} + +module_init(da9052_gpio_init); +module_exit(da9052_gpio_exit); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("DA9052 GPIO Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/hwmon/da9052-adc.c b/drivers/hwmon/da9052-adc.c new file mode 100644 index 000000000000..647e580e2131 --- /dev/null +++ b/drivers/hwmon/da9052-adc.c @@ -0,0 +1,644 @@ +/* + * da9052-adc.c -- ADC Driver for Dialog DA9052 + * + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * Author: Dialog Semiconductor Ltd <dchen@diasemi.com> + * + * 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. + * + */ +#include <linux/platform_device.h> +#include <linux/hwmon-sysfs.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/hwmon.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/adc.h> + +#define DRIVER_NAME "da9052-adc" + +static const char *input_names[] = { + [DA9052_ADC_VDDOUT] = "VDDOUT", + [DA9052_ADC_ICH] = "CHARGING CURRENT", + [DA9052_ADC_TBAT] = "BATTERY TEMP", + [DA9052_ADC_VBAT] = "BATTERY VOLTAGE", + [DA9052_ADC_ADCIN4] = "ADC INPUT 4", + [DA9052_ADC_ADCIN5] = "ADC INPUT 5", + [DA9052_ADC_ADCIN6] = "ADC INPUT 6", + [DA9052_ADC_TSI] = "TSI", + [DA9052_ADC_TJUNC] = "BATTERY JUNCTION TEMP", + [DA9052_ADC_VBBAT] = "BACK-UP BATTERY TEMP", +}; + + +int da9052_manual_read(struct da9052 *da9052, + unsigned char channel) +{ + unsigned char man_timeout_cnt = DA9052_ADC_MAX_MANCONV_RETRY_COUNT; + struct da9052_ssc_msg msg; + unsigned short calc_data; + unsigned int ret; + u16 data = 0; + + msg.addr = DA9052_ADCMAN_REG; + msg.data = channel; + msg.data = (msg.data | DA9052_ADCMAN_MANCONV); + + mutex_lock(&da9052->manconv_lock); + da9052_lock(da9052); + + ret = da9052->write(da9052, &msg); + if (ret) + goto err_ssc_comm; + da9052_unlock(da9052); + + /* Wait for the event */ + do { + msg.addr = DA9052_ADCCONT_REG; + msg.data = 0; + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret) + goto err_ssc_comm; + da9052_unlock(da9052); + + if (DA9052_ADCCONT_ADCMODE & msg.data) + msleep(1); + else + msleep(10); + + msg.addr = DA9052_ADCMAN_REG; + msg.data = 0; + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret) + goto err_ssc_comm; + da9052_unlock(da9052); + + /* Counter to avoid endless while loop */ + man_timeout_cnt--; + if (man_timeout_cnt == 1) { + if (!(msg.data & DA9052_ADCMAN_MANCONV)) + break; + else + goto err_ssc_comm; + } + /* Wait until the MAN_CONV bit is cleared to zero */ + } while (msg.data & DA9052_ADCMAN_MANCONV); + + msg.addr = DA9052_ADCRESH_REG; + msg.data = 0; + + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret) + goto err_ssc_comm; + da9052_unlock(da9052); + + calc_data = (unsigned short)msg.data; + data = (calc_data << 2); + + msg.addr = DA9052_ADCRESL_REG; + msg.data = 0; + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret) + goto err_ssc_comm; + da9052_unlock(da9052); + + /* Clear first 14 bits before ORing */ + calc_data = (unsigned short)msg.data & 0x0003; + data |= calc_data; + + mutex_unlock(&da9052->manconv_lock); + + return data; +err_ssc_comm: + mutex_unlock(&da9052->manconv_lock); + da9052_unlock(da9052); + return -EIO; +} +EXPORT_SYMBOL(da9052_manual_read); + +int da9052_read_tjunc(struct da9052 *da9052, char *buf) +{ + struct da9052_ssc_msg msg; + unsigned char temp; + int ret; + + msg.addr = DA9052_TJUNCRES_REG; + msg.data = 0; + + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret) + goto err_ssc_comm; + + temp = msg.data; + + msg.addr = DA9052_TOFFSET_REG; + msg.data = 0; + ret = da9052->read(da9052, &msg); + if (ret) + goto err_ssc_comm; + da9052_unlock(da9052); + /* Calculate Junction temperature */ + temp = (temp - msg.data); + *buf = temp; + return 0; +err_ssc_comm: + da9052_unlock(da9052); + return -EIO; +} +EXPORT_SYMBOL(da9052_read_tjunc); + +int da9052_read_tbat_ich(struct da9052 *da9052, char *data, int channel_no) +{ + struct da9052_ssc_msg msg; + int ret; + + /* Read TBAT conversion result */ + switch (channel_no) { + case DA9052_ADC_TBAT: + msg.addr = DA9052_TBATRES_REG; + break; + case DA9052_ADC_ICH: + msg.addr = DA9052_ICHGAV_REG; + break; + default: + return -EINVAL; + } + msg.data = 0; + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret) + goto err_ssc_comm; + da9052_unlock(da9052); + *data = msg.data; + printk(KERN_INFO"msg.data 1= %d\n", msg.data); + msg.data = 28; + da9052_lock(da9052); + ret = da9052->write(da9052, &msg); + if (ret) + goto err_ssc_comm; + da9052_unlock(da9052); + printk(KERN_INFO"msg.data2 = %d\n", msg.data); + msg.data = 0; + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret) + goto err_ssc_comm; + da9052_unlock(da9052); + printk(KERN_INFO"msg.data3 = %d\n", msg.data); + return 0; + +err_ssc_comm: + da9052_unlock(da9052); + return ret; +} +EXPORT_SYMBOL(da9052_read_tbat_ich); + +static int da9052_start_adc(struct da9052 *da9052, unsigned channel) +{ + struct da9052_ssc_msg msg; + int ret; + + msg.addr = DA9052_ADCCONT_REG; + msg.data = 0; + + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret != 0) + goto err_ssc_comm; + + if (channel == DA9052_ADC_VDDOUT) + msg.data = (msg.data | DA9052_ADCCONT_AUTOVDDEN); + else if (channel == DA9052_ADC_ADCIN4) + msg.data = (msg.data | DA9052_ADCCONT_AUTOAD4EN); + else if (channel == DA9052_ADC_ADCIN5) + msg.data = (msg.data | DA9052_ADCCONT_AUTOAD5EN); + else if (channel == DA9052_ADC_ADCIN6) + msg.data = (msg.data | DA9052_ADCCONT_AUTOAD6EN); + else + return -EINVAL; + + ret = da9052->write(da9052, &msg); + if (ret != 0) + goto err_ssc_comm; + da9052_unlock(da9052); + return 0; + +err_ssc_comm: + da9052_unlock(da9052); + return -EIO; +} + +static int da9052_stop_adc(struct da9052 *da9052, unsigned channel) +{ + int ret; + struct da9052_ssc_msg msg; + + msg.addr = DA9052_ADCCONT_REG; + msg.data = 0; + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret != 0) + goto err_ssc_comm; + + if (channel == DA9052_ADC_VDDOUT) + msg.data = (msg.data & ~(DA9052_ADCCONT_AUTOVDDEN)); + else if (channel == DA9052_ADC_ADCIN4) + msg.data = (msg.data & ~(DA9052_ADCCONT_AUTOAD4EN)); + else if (channel == DA9052_ADC_ADCIN5) + msg.data = (msg.data & ~(DA9052_ADCCONT_AUTOAD5EN)); + else if (channel == DA9052_ADC_ADCIN6) + msg.data = (msg.data & ~(DA9052_ADCCONT_AUTOAD6EN)); + else + return -EINVAL; + + ret = da9052->write(da9052, &msg); + if (ret != 0) + goto err_ssc_comm; + da9052_unlock(da9052); + + return 0; +err_ssc_comm: + da9052_unlock(da9052); + return -EIO; +} + +static ssize_t da9052_adc_read_start_stop(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct da9052_adc_priv *priv = platform_get_drvdata(pdev); + struct da9052_ssc_msg msg; + int channel = to_sensor_dev_attr(devattr)->index; + int ret; + + ret = da9052_start_adc(priv->da9052, channel); + if (ret < 0) + return ret; + + /* Read the ADC converted value */ + switch (channel) { + case DA9052_ADC_VDDOUT: + msg.addr = DA9052_VDDRES_REG; + break; +#if (DA9052_ADC_CONF_ADC4 == 1) + case DA9052_ADC_ADCIN4: + msg.addr = DA9052_ADCIN4RES_REG; + break; +#endif +#if (DA9052_ADC_CONF_ADC5 == 1) + case DA9052_ADC_ADCIN5: + msg.addr = DA9052_ADCIN5RES_REG; + break; +#endif +#if (DA9052_ADC_CONF_ADC6 == 1) + case DA9052_ADC_ADCIN6: + msg.addr = DA9052_ADCIN6RES_REG; + break; +#endif + default: + return -EINVAL; + } + msg.data = 0; + da9052_lock(priv->da9052); + ret = priv->da9052->read(priv->da9052, &msg); + if (ret != 0) + goto err_ssc_comm; + da9052_unlock(priv->da9052); + + ret = da9052_stop_adc(priv->da9052, channel); + if (ret < 0) + return ret; + + return sprintf(buf, "%u\n", msg.data); + +err_ssc_comm: + da9052_unlock(priv->da9052); + return ret; +} + +static ssize_t da9052_adc_read_ich(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct da9052_adc_priv *priv = platform_get_drvdata(pdev); + int ret; + + ret = da9052_read_tbat_ich(priv->da9052, buf, DA9052_ADC_ICH); + if (ret < 0) + return ret; + return sprintf(buf, "%u\n", *buf); +} + +static ssize_t da9052_adc_read_tbat(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct da9052_adc_priv *priv = platform_get_drvdata(pdev); + int ret; + + ret = da9052_read_tbat_ich(priv->da9052, buf, DA9052_ADC_TBAT); + if (ret < 0) + return ret; + return sprintf(buf, "%u\n", *buf); +} + +static ssize_t da9052_adc_read_vbat(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct da9052_adc_priv *priv = platform_get_drvdata(pdev); + s32 ret; + + ret = da9052_manual_read(priv->da9052, DA9052_ADC_VBAT); + if (ret < 0) + return ret; + return sprintf(buf, "%u\n", ret); +} + +static ssize_t da9052_adc_read_tjunc(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct da9052_adc_priv *priv = platform_get_drvdata(pdev); + int ret; + ret = da9052_read_tjunc(priv->da9052, buf); + if (ret < 0) + return ret; + return sprintf(buf, "%u\n", *buf); +} + +static ssize_t da9052_adc_read_vbbat(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct da9052_adc_priv *priv = platform_get_drvdata(pdev); + s32 ret; + + ret = da9052_manual_read(priv->da9052, DA9052_ADC_VBBAT); + if (ret < 0) + return ret; + return sprintf(buf, "%u\n", ret); +} + +static int da9052_adc_hw_init(struct da9052 *da9052) +{ + struct da9052_ssc_msg msg; + int ret; + + /* ADC channel 4 and 5 are by default enabled */ +#if (DA9052_ADC_CONF_ADC4 == 1) + msg.addr = DA9052_GPIO0001_REG; + msg.data = 0; + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret) + goto err_ssc_comm; + + msg.data = (msg.data & ~(DA9052_GPIO0001_GPIO0PIN)); + ret = da9052->write(da9052, &msg); + if (ret != 0) + goto err_ssc_comm; + da9052_unlock(da9052); +#endif + +#if (DA9052_ADC_CONF_ADC5 == 1) + msg.addr = DA9052_GPIO0001_REG; + msg.data = 0; + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret) + goto err_ssc_comm; + + msg.data = (msg.data & ~(DA9052_GPIO0001_GPIO0PIN)); + ret = da9052->write(da9052, &msg); + if (ret != 0) + goto err_ssc_comm; + da9052_unlock(da9052); +#endif + +#if (DA9052_ADC_CONF_ADC6 == 1) + msg.addr = DA9052_GPIO0203_REG; + msg.data = 0; + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret) + goto err_ssc_comm; + + msg.data = (msg.data & ~(DA9052_GPIO0203_GPIO2PIN)); + ret = da9052->write(da9052, &msg); + if (ret != 0) + goto err_ssc_comm; + da9052_unlock(da9052); +#endif +#if 0 + /* By default configure the Measurement sequence interval to 10ms */ + msg.addr = DA9052_ADCCONT_REG; + msg.data = 0; + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret != 0) + goto err_ssc_comm; + + /* Set the ADC MODE bit for 10msec sampling timer */ + msg.data = (msg.data & ~(DA9052_ADCCONT_ADCMODE)); + ret = da9052->write(da9052, &msg); + if (ret != 0) + goto err_ssc_comm; + da9052_unlock(da9052); +#endif + return 0; +err_ssc_comm: + da9052_unlock(da9052); + return -EIO; +} + +static ssize_t da9052_adc_show_name(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + return sprintf(buf, "da9052-adc\n"); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int channel = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%s\n", input_names[channel]); +} +#define DA9052_ADC_CHANNELS(id, name) \ + static SENSOR_DEVICE_ATTR(in##id##_label, S_IRUGO, show_label, \ + NULL, name) + +DA9052_ADC_CHANNELS(0, DA9052_ADC_VDDOUT); +DA9052_ADC_CHANNELS(1, DA9052_ADC_ICH); +DA9052_ADC_CHANNELS(2, DA9052_ADC_TBAT); +DA9052_ADC_CHANNELS(3, DA9052_ADC_VBAT); +#if (DA9052_ADC_CONF_ADC4 == 1) +DA9052_ADC_CHANNELS(4, DA9052_ADC_ADCIN4); +#endif +#if (DA9052_ADC_CONF_ADC5 == 1) +DA9052_ADC_CHANNELS(5, DA9052_ADC_ADCIN5); +#endif +#if (DA9052_ADC_CONF_ADC6 == 1) +DA9052_ADC_CHANNELS(6, DA9052_ADC_ADCIN6); +#endif +DA9052_ADC_CHANNELS(7, DA9052_ADC_TSI); +DA9052_ADC_CHANNELS(8, DA9052_ADC_TJUNC); +DA9052_ADC_CHANNELS(9, DA9052_ADC_VBBAT); + + +static DEVICE_ATTR(name, S_IRUGO, da9052_adc_show_name, NULL); +static SENSOR_DEVICE_ATTR(read_vddout, S_IRUGO, + da9052_adc_read_start_stop, NULL, + DA9052_ADC_VDDOUT); +static SENSOR_DEVICE_ATTR(read_ich, S_IRUGO, da9052_adc_read_ich, NULL, + DA9052_ADC_ICH); +static SENSOR_DEVICE_ATTR(read_tbat, S_IRUGO, da9052_adc_read_tbat, NULL, + DA9052_ADC_TBAT); +static SENSOR_DEVICE_ATTR(read_vbat, S_IRUGO, da9052_adc_read_vbat, NULL, + DA9052_ADC_VBAT); +#if (DA9052_ADC_CONF_ADC4 == 1) +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, da9052_adc_read_start_stop, NULL, + DA9052_ADC_ADCIN4); +#endif +#if (DA9052_ADC_CONF_ADC5 == 1) +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, da9052_adc_read_start_stop, NULL, + DA9052_ADC_ADCIN5); +#endif +#if (DA9052_ADC_CONF_ADC6 == 1) +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, da9052_adc_read_start_stop, NULL, + DA9052_ADC_ADCIN6); +#endif +static SENSOR_DEVICE_ATTR(read_tjunc, S_IRUGO, da9052_adc_read_tjunc, NULL, + DA9052_ADC_TJUNC); +static SENSOR_DEVICE_ATTR(read_vbbat, S_IRUGO, da9052_adc_read_vbbat, NULL, + DA9052_ADC_VBBAT); + +static struct attribute *da9052_attr[] = { + &dev_attr_name.attr, + &sensor_dev_attr_read_vddout.dev_attr.attr, + &sensor_dev_attr_in0_label.dev_attr.attr, + &sensor_dev_attr_read_ich.dev_attr.attr, + &sensor_dev_attr_in1_label.dev_attr.attr, + &sensor_dev_attr_read_tbat.dev_attr.attr, + &sensor_dev_attr_in2_label.dev_attr.attr, + &sensor_dev_attr_read_vbat.dev_attr.attr, + &sensor_dev_attr_in3_label.dev_attr.attr, +#if (DA9052_ADC_CONF_ADC4 == 1) + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_label.dev_attr.attr, +#endif +#if (DA9052_ADC_CONF_ADC5 == 1) + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_label.dev_attr.attr, +#endif +#if (DA9052_ADC_CONF_ADC6 == 1) + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_label.dev_attr.attr, +#endif + &sensor_dev_attr_in7_label.dev_attr.attr, + &sensor_dev_attr_read_tjunc.dev_attr.attr, + &sensor_dev_attr_in8_label.dev_attr.attr, + &sensor_dev_attr_read_vbbat.dev_attr.attr, + &sensor_dev_attr_in9_label.dev_attr.attr, + NULL +}; + +static const struct attribute_group da9052_group = { + .attrs = da9052_attr, +}; + +static int __init da9052_adc_probe(struct platform_device *pdev) +{ + struct da9052_adc_priv *priv; + int ret; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->da9052 = dev_get_drvdata(pdev->dev.parent); + + platform_set_drvdata(pdev, priv); + + /* Register sysfs hooks */ + ret = sysfs_create_group(&pdev->dev.kobj, &da9052_group); + if (ret) + goto out_err_create1; + + priv->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(priv->hwmon_dev)) { + ret = PTR_ERR(priv->hwmon_dev); + goto out_err_create2; + } + /* Initializes the hardware for ADC module */ + da9052_adc_hw_init(priv->da9052); + + /* Initialize mutex required for ADC Manual read */ + mutex_init(&priv->da9052->manconv_lock); + + return 0; + +out_err_create2: + sysfs_remove_group(&pdev->dev.kobj, &da9052_group); +out_err_create1: + platform_set_drvdata(pdev, NULL); + kfree(priv); + + return ret; +} + +static int __devexit da9052_adc_remove(struct platform_device *pdev) +{ + struct da9052_adc_priv *priv = platform_get_drvdata(pdev); + + mutex_destroy(&priv->da9052->manconv_lock); + + hwmon_device_unregister(priv->hwmon_dev); + + sysfs_remove_group(&pdev->dev.kobj, &da9052_group); + + platform_set_drvdata(pdev, NULL); + kfree(priv); + + return 0; +} + +static struct platform_driver da9052_adc_driver = { + .remove = __devexit_p(da9052_adc_remove), + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, +}; + +static int __init da9052_adc_init(void) +{ + return platform_driver_probe(&da9052_adc_driver, da9052_adc_probe); +} +module_init(da9052_adc_init); + +static void __exit da9052_adc_exit(void) +{ + platform_driver_unregister(&da9052_adc_driver); +} +module_exit(da9052_adc_exit); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>") +MODULE_DESCRIPTION("DA9052 ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/input/misc/da9052_onkey.c b/drivers/input/misc/da9052_onkey.c new file mode 100644 index 000000000000..2271b59261fa --- /dev/null +++ b/drivers/input/misc/da9052_onkey.c @@ -0,0 +1,133 @@ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/platform_device.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> + +#define DRIVER_NAME "da9052-onkey" + +struct da9052_onkey_data { + struct da9052 *da9052; + struct da9052_eh_nb eh_data; + struct input_dev *input; +}; + +static void da9052_onkey_report_event(struct da9052_eh_nb *eh_data, + unsigned int event) +{ + struct da9052_onkey_data *da9052_onkey = + container_of(eh_data, struct da9052_onkey_data, eh_data); + struct da9052_ssc_msg msg; + unsigned int ret; + + /* Read the Evnet Register */ + msg.addr = DA9052_EVENTB_REG; + da9052_lock(da9052_onkey->da9052); + ret = da9052_onkey->da9052->read(da9052_onkey->da9052, &msg); + if (ret) { + da9052_unlock(da9052_onkey->da9052); + return; + } + da9052_unlock(da9052_onkey->da9052); + msg.data = msg.data & DA9052_EVENTB_ENONKEY; + + input_report_key(da9052_onkey->input, KEY_POWER, msg.data); + input_sync(da9052_onkey->input); + printk(KERN_INFO "DA9052 ONKEY EVENT REPORTED \n"); +} + +static int __devinit da9052_onkey_probe(struct platform_device *pdev) +{ + struct da9052_onkey_data *da9052_onkey; + int error; + + da9052_onkey = kzalloc(sizeof(*da9052_onkey), GFP_KERNEL); + da9052_onkey->input = input_allocate_device(); + if (!da9052_onkey->input) { + dev_err(&pdev->dev, "failed to allocate data device\n"); + error = -ENOMEM; + goto fail1; + } + da9052_onkey->da9052 = dev_get_drvdata(pdev->dev.parent); + + if (!da9052_onkey->input) { + dev_err(&pdev->dev, "failed to allocate input device\n"); + error = -ENOMEM; + goto fail2; + } + + da9052_onkey->input->evbit[0] = BIT_MASK(EV_KEY); + da9052_onkey->input->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER); + da9052_onkey->input->name = "da9052-onkey"; + da9052_onkey->input->phys = "da9052-onkey/input0"; + da9052_onkey->input->dev.parent = &pdev->dev; + + /* Set the EH structure */ + da9052_onkey->eh_data.eve_type = ONKEY_EVE; + da9052_onkey->eh_data.call_back = &da9052_onkey_report_event; + error = da9052_onkey->da9052->register_event_notifier( + da9052_onkey->da9052, + &da9052_onkey->eh_data); + if (error) + goto fail2; + + error = input_register_device(da9052_onkey->input); + if (error) { + dev_err(&pdev->dev, "Unable to register input\ + device,error: %d\n", error); + goto fail3; + } + + platform_set_drvdata(pdev, da9052_onkey); + + return 0; + +fail3: + da9052_onkey->da9052->unregister_event_notifier(da9052_onkey->da9052, + &da9052_onkey->eh_data); +fail2: + input_free_device(da9052_onkey->input); +fail1: + kfree(da9052_onkey); + return error; +} + +static int __devexit da9052_onkey_remove(struct platform_device *pdev) +{ + struct da9052_onkey_data *da9052_onkey = pdev->dev.platform_data; + da9052_onkey->da9052->unregister_event_notifier(da9052_onkey->da9052, + &da9052_onkey->eh_data); + input_unregister_device(da9052_onkey->input); + kfree(da9052_onkey); + + return 0; +} + +static struct platform_driver da9052_onkey_driver = { + .probe = da9052_onkey_probe, + .remove = __devexit_p(da9052_onkey_remove), + .driver = { + .name = "da9052-onkey", + .owner = THIS_MODULE, + } +}; + +static int __init da9052_onkey_init(void) +{ + return platform_driver_register(&da9052_onkey_driver); +} + +static void __exit da9052_onkey_exit(void) +{ + platform_driver_unregister(&da9052_onkey_driver); +} + +module_init(da9052_onkey_init); +module_exit(da9052_onkey_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("Onkey driver for DA9052"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/input/touchscreen/da9052_tsi.c b/drivers/input/touchscreen/da9052_tsi.c new file mode 100644 index 000000000000..3b79cf3debfa --- /dev/null +++ b/drivers/input/touchscreen/da9052_tsi.c @@ -0,0 +1,1448 @@ +/* + * da9052_tsi.c -- TSI driver for Dialog DA9052 + * + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * Author: Dialog Semiconductor Ltd <dchen@diasemi.com> + * + * 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. + * + */ +#include <linux/module.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/freezer.h> +#include <linux/kthread.h> + +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/tsi_cfg.h> +#include <linux/mfd/da9052/tsi.h> +#include <linux/mfd/da9052/gpio.h> +#include <linux/mfd/da9052/adc.h> + +#define WAIT_FOR_PEN_DOWN 0 +#define WAIT_FOR_SAMPLING 1 +#define SAMPLING_ACTIVE 2 + +static ssize_t __init da9052_tsi_create_input_dev(struct input_dev **ip_dev, + u8 n); +static ssize_t read_da9052_reg(struct da9052 *da9052, u8 reg_addr); +static ssize_t write_da9052_reg(struct da9052 *da9052, u8 reg_addr, u8 data); + +static void da9052_tsi_reg_pendwn_event(struct da9052_ts_priv *priv); +static void da9052_tsi_reg_datardy_event(struct da9052_ts_priv *priv); +static ssize_t da9052_tsi_config_delay(struct da9052_ts_priv *priv, + enum TSI_DELAY delay); +static ssize_t da9052_tsi_config_measure_seq(struct da9052_ts_priv *priv, + enum TSI_MEASURE_SEQ seq); +static ssize_t da9052_tsi_config_state(struct da9052_ts_priv *ts, + enum TSI_STATE state); +static ssize_t da9052_tsi_set_sampling_mode(struct da9052_ts_priv *priv, + u8 interval); +static ssize_t da9052_tsi_config_skip_slots(struct da9052_ts_priv *priv, + enum TSI_SLOT_SKIP skip); +static ssize_t da9052_tsi_config_pen_detect(struct da9052_ts_priv *priv, + u8 flag); +static ssize_t da9052_tsi_disable_irq(struct da9052_ts_priv *priv, + enum TSI_IRQ tsi_irq); +static ssize_t da9052_tsi_enable_irq(struct da9052_ts_priv *priv, + enum TSI_IRQ tsi_irq); +static ssize_t da9052_tsi_config_manual_mode(struct da9052_ts_priv *priv, + u8 coordinate); +static ssize_t da9052_tsi_config_auto_mode(struct da9052_ts_priv *priv, + u8 state); +static ssize_t da9052_tsi_config_gpio(struct da9052_ts_priv *priv); +static ssize_t da9052_tsi_config_power_supply(struct da9052_ts_priv *priv, + u8 state); +static struct da9052_tsi_info *get_tsi_drvdata(void); +static void da9052_tsi_penup_event(struct da9052_ts_priv *priv); +static s32 da9052_tsi_get_rawdata(struct da9052_tsi_reg *buf, u8 cnt); +static ssize_t da9052_tsi_reg_proc_thread(void *ptr); +static ssize_t da9052_tsi_resume(struct platform_device *dev); +static ssize_t da9052_tsi_suspend(struct platform_device *dev, + pm_message_t state); +struct da9052_tsi tsi_reg; +struct da9052_tsi_info gda9052_tsi_info; + +static ssize_t write_da9052_reg(struct da9052 *da9052, u8 reg_addr, u8 data) +{ + ssize_t ret = 0; + struct da9052_ssc_msg ssc_msg; + + ssc_msg.addr = reg_addr; + ssc_msg.data = data; + ret = da9052->write(da9052, &ssc_msg); + if (ret) { + DA9052_DEBUG("%s: ",__FUNCTION__); + DA9052_DEBUG("da9052_ssc_write Failed %d\n",ret ); + } + + return ret; +} + +static ssize_t read_da9052_reg(struct da9052 *da9052, u8 reg_addr) +{ + ssize_t ret = 0; + struct da9052_ssc_msg ssc_msg; + + ssc_msg.addr = reg_addr; + ssc_msg.data = 0; + ret = da9052->read(da9052, &ssc_msg); + if (ret) { + DA9052_DEBUG("%s: ",__FUNCTION__); + DA9052_DEBUG("da9052_ssc_read Failed => %d\n" ,ret); + return -ret; + } + return ssc_msg.data; +} + +static struct da9052_tsi_info *get_tsi_drvdata(void) +{ + return &gda9052_tsi_info; +} + +static ssize_t da9052_tsi_config_measure_seq(struct da9052_ts_priv *priv, + enum TSI_MEASURE_SEQ seq) +{ + ssize_t ret = 0; + u8 data = 0; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + if (seq > 1) + return -EINVAL; + + da9052_lock(priv->da9052); + ret = read_da9052_reg(priv->da9052, DA9052_TSICONTA_REG); + if (ret < 0) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG("read_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + + data = (u8)ret; + + if (seq == XYZP_MODE) + data = enable_xyzp_mode(data); + else if (seq == XP_MODE) + data = enable_xp_mode(data); + else { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG("Invalid Value passed \n" ); + da9052_unlock(priv->da9052); + return -EINVAL; + } + + ret = write_da9052_reg(priv->da9052, DA9052_TSICONTA_REG, data); + if (ret) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG(" write_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + da9052_unlock(priv->da9052); + + ts->tsi_conf.auto_cont.da9052_tsi_cont_a = data; + + return 0; +} + +static ssize_t da9052_tsi_set_sampling_mode(struct da9052_ts_priv *priv, + u8 mode) +{ + u8 data = 0; + ssize_t ret = 0; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + da9052_lock(priv->da9052); + + ret = read_da9052_reg(priv->da9052, DA9052_ADCCONT_REG); + if (ret < 0) { + DA9052_DEBUG("DA9052_TSI:%s:", __FUNCTION__); + DA9052_DEBUG("read_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + data = (u8)ret; + + if (mode == ECONOMY_MODE) + data = adc_mode_economy_mode(data); + else if (mode == FAST_MODE) + data = adc_mode_fast_mode(data); + else { + DA9052_DEBUG("DA9052_TSI:%s:", __FUNCTION__); + DA9052_DEBUG("Invalid interval passed \n" ); + da9052_unlock(priv->da9052); + return -EINVAL; + } + + ret = write_da9052_reg(priv->da9052, DA9052_ADCCONT_REG, data); + if (ret) { + DA9052_DEBUG("DA9052_TSI:%s:", __FUNCTION__); + DA9052_DEBUG("write_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + da9052_unlock(priv->da9052); + + switch (mode) { + case ECONOMY_MODE: + priv->tsi_reg_data_poll_interval = + TSI_ECO_MODE_REG_DATA_PROCESSING_INTERVAL; + priv->tsi_raw_data_poll_interval = + TSI_ECO_MODE_RAW_DATA_PROCESSING_INTERVAL; + break; + case FAST_MODE: + priv->tsi_reg_data_poll_interval = + TSI_FAST_MODE_REG_DATA_PROCESSING_INTERVAL; + priv->tsi_raw_data_poll_interval = + TSI_FAST_MODE_RAW_DATA_PROCESSING_INTERVAL; + break; + default: + DA9052_DEBUG("DA9052_TSI:%s:", __FUNCTION__); + DA9052_DEBUG("Invalid interval passed \n" ); + return -EINVAL; + } + + ts->tsi_penup_count = + (u32)priv->tsi_pdata->pen_up_interval / + priv->tsi_reg_data_poll_interval; + + return 0; +} + +static ssize_t da9052_tsi_config_delay(struct da9052_ts_priv *priv, + enum TSI_DELAY delay) +{ + ssize_t ret = 0; + u8 data = 0; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + if (delay > priv->tsi_pdata->max_tsi_delay) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG(" invalid value for tsi delay!!!\n" ); + return -EINVAL; + } + + da9052_lock(priv->da9052); + + ret = read_da9052_reg(priv->da9052, DA9052_TSICONTA_REG); + if(ret < 0) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG("read_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + + data = clear_bits((u8)ret, DA9052_TSICONTA_TSIDELAY); + + data = set_bits(data, (delay << priv->tsi_pdata->tsi_delay_bit_shift)); + + ret = write_da9052_reg(priv->da9052, DA9052_TSICONTA_REG, data); + if (ret) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG(" write_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + da9052_unlock(priv->da9052); + + ts->tsi_conf.auto_cont.da9052_tsi_cont_a = data; + + return 0; +} + +ssize_t da9052_tsi_config_skip_slots(struct da9052_ts_priv *priv, + enum TSI_SLOT_SKIP skip) +{ + ssize_t ret = 0; + u8 data = 0; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + if (skip > priv->tsi_pdata->max_tsi_skip_slot) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG(" invalid value for tsi skip slots!!!\n" ); + return -EINVAL; + } + + da9052_lock(priv->da9052); + + ret = read_da9052_reg(priv->da9052, DA9052_TSICONTA_REG); + if (ret < 0) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG("read_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + + data = clear_bits((u8)ret, DA9052_TSICONTA_TSISKIP); + + data = set_bits(data, (skip << priv->tsi_pdata->tsi_skip_bit_shift)); + + ret = write_da9052_reg(priv->da9052, DA9052_TSICONTA_REG, data); + if (ret) { + DA9052_DEBUG("DA9052_TSI:da9052_tsi_config_skip_slots:"); + DA9052_DEBUG(" write_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + da9052_unlock(priv->da9052); + + ts->tsi_conf.auto_cont.da9052_tsi_cont_a = data; + + return 0; +} + +static ssize_t da9052_tsi_config_state(struct da9052_ts_priv *priv, + enum TSI_STATE state) +{ + s32 ret; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + if (ts->tsi_conf.state == state) + return 0; + + switch (state) { + case TSI_AUTO_MODE: + ts->tsi_zero_data_cnt = 0; + priv->early_data_flag = TRUE; + priv->debounce_over = FALSE; + priv->win_reference_valid = FALSE; + + clean_tsi_fifos(priv); + + ret = da9052_tsi_config_auto_mode(priv, DISABLE); + if (ret) + return ret; + + ret = da9052_tsi_config_manual_mode(priv, DISABLE); + if (ret) + return ret; + + ret = da9052_tsi_config_power_supply(priv, DISABLE); + if (ret) + return ret; + + ret = da9052_tsi_enable_irq(priv, TSI_PEN_DWN); + if (ret) + return ret; + ts->tsi_conf.tsi_pendown_irq_mask = RESET; + + ret = da9052_tsi_disable_irq(priv, TSI_DATA_RDY); + if (ret) + return ret; + ts->tsi_conf.tsi_ready_irq_mask = SET; + + da9052_tsi_reg_pendwn_event(priv); + da9052_tsi_reg_datardy_event(priv); + + ret = da9052_tsi_config_pen_detect(priv, ENABLE); + if (ret) + return ret; + break; + + case TSI_IDLE: + ts->pen_dwn_event = RESET; + + ret = da9052_tsi_config_pen_detect(priv, DISABLE); + if (ret) + return ret; + + ret = da9052_tsi_config_auto_mode(priv, DISABLE); + if (ret) + return ret; + + ret = da9052_tsi_config_manual_mode(priv, DISABLE); + if (ret) + return ret; + + ret = da9052_tsi_config_power_supply(priv, DISABLE); + if (ret) + return ret; + + if (ts->pd_reg_status) { + priv->da9052->unregister_event_notifier(priv->da9052, + &priv->pd_nb); + ts->pd_reg_status = RESET; + } + break; + + default: + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG(" Invalid state passed"); + return -EINVAL; + } + + ts->tsi_conf.state = state; + + return 0; +} + +static void da9052_tsi_reg_pendwn_event(struct da9052_ts_priv *priv) +{ + ssize_t ret = 0; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + if (ts->pd_reg_status) { + DA9052_DEBUG("%s: Pen down ",__FUNCTION__); + DA9052_DEBUG("Registeration is already done \n"); + return; + } + + priv->pd_nb.eve_type = PEN_DOWN_EVE; + priv->pd_nb.call_back = &da9052_tsi_pen_down_handler; + + ret = priv->da9052->register_event_notifier(priv->da9052, &priv->pd_nb); + if (ret) { + DA9052_DEBUG("%s: EH Registeration",__FUNCTION__); + DA9052_DEBUG(" Failed: ret = %d\n",ret ); + ts->pd_reg_status = RESET; + } else + ts->pd_reg_status = SET; + + priv->os_data_cnt = 0; + priv->raw_data_cnt = 0; + + return; +} + +static void da9052_tsi_reg_datardy_event(struct da9052_ts_priv *priv) +{ + ssize_t ret = 0; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + if(ts->datardy_reg_status) + { + DA9052_DEBUG("%s: Data Ready ",__FUNCTION__); + DA9052_DEBUG("Registeration is already done \n"); + return; + } + + priv->datardy_nb.eve_type = TSI_READY_EVE; + priv->datardy_nb.call_back = &da9052_tsi_data_ready_handler; + + ret = priv->da9052->register_event_notifier(priv->da9052, + &priv->datardy_nb); + + if(ret) + { + DA9052_DEBUG("%s: EH Registeration",__FUNCTION__); + DA9052_DEBUG(" Failed: ret = %d\n",ret ); + ts->datardy_reg_status = RESET; + } else + ts->datardy_reg_status = SET; + + return; +} + +static ssize_t __init da9052_tsi_create_input_dev(struct input_dev **ip_dev, + u8 n) +{ + u8 i; + s32 ret; + struct input_dev *dev = NULL; + + if (!n) + return -EINVAL; + + for (i = 0; i < n; i++) { + dev = input_allocate_device(); + if (!dev) { + DA9052_DEBUG(KERN_ERR "%s:%s():memory allocation for \ + inputdevice failed\n", __FILE__, + __FUNCTION__); + return -ENOMEM; + } + + ip_dev[i] = dev; + switch (i) { + case TSI_INPUT_DEVICE_OFF: + dev->name = DA9052_TSI_INPUT_DEV; + dev->phys = "input(tsi)"; + break; + default: + break; + } + } + dev->id.vendor = DA9052_VENDOR_ID; + dev->id.product = DA9052_PRODUCT_ID; + dev->id.bustype = BUS_RS232; + dev->id.version = TSI_VERSION; + dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + dev->evbit[0] = (BIT_MASK(EV_SYN) | + BIT_MASK(EV_KEY) | + BIT_MASK(EV_ABS)); + + input_set_abs_params(dev, ABS_X, 0, DA9052_DISPLAY_X_MAX, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, DA9052_DISPLAY_Y_MAX, 0, 0); + input_set_abs_params(dev, ABS_PRESSURE, 0, DA9052_TOUCH_PRESSURE_MAX, + 0, 0); + + ret = input_register_device(dev); + if (ret) { + DA9052_DEBUG(KERN_ERR "%s: Could ", __FUNCTION__); + DA9052_DEBUG("not register input device(touchscreen)!\n"); + ret = -EIO; + goto fail; + } + return 0; + +fail: + for (; i-- != 0; ) + input_free_device(ip_dev[i]); + return -EINVAL; +} + +static ssize_t __init da9052_tsi_init_drv(struct da9052_ts_priv *priv) +{ + u8 cnt = 0; + ssize_t ret = 0; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + if ((DA9052_GPIO_PIN_3 != DA9052_GPIO_CONFIG_TSI) || + (DA9052_GPIO_PIN_4 != DA9052_GPIO_CONFIG_TSI) || + (DA9052_GPIO_PIN_5 != DA9052_GPIO_CONFIG_TSI) || + (DA9052_GPIO_PIN_6 != DA9052_GPIO_CONFIG_TSI) || + (DA9052_GPIO_PIN_7 != DA9052_GPIO_CONFIG_TSI)) { + printk(KERN_ERR"DA9052_TSI: Configure DA9052 GPIO "); + printk(KERN_ERR"pins for TSI\n"); + return -EINVAL; + } + + ret = da9052_tsi_config_gpio(priv); + + ret = da9052_tsi_config_state(priv, TSI_IDLE); + ts->tsi_conf.state = TSI_IDLE; + + da9052_tsi_config_measure_seq(priv, TSI_MODE_VALUE); + + da9052_tsi_config_skip_slots(priv, TSI_SLOT_SKIP_VALUE); + + da9052_tsi_config_delay(priv, TSI_DELAY_VALUE); + + da9052_tsi_set_sampling_mode(priv, DEFAULT_TSI_SAMPLING_MODE); + + ts->tsi_calib = get_calib_config(); + + ret = da9052_tsi_create_input_dev(ts->input_devs, NUM_INPUT_DEVS); + if (ret) { + DA9052_DEBUG("DA9052_TSI: %s: ", __FUNCTION__); + DA9052_DEBUG("da9052_tsi_create_input_dev Failed \n" ); + return ret; + } + + da9052_init_tsi_fifos(priv); + + init_completion(&priv->tsi_reg_proc_thread.notifier); + priv->tsi_reg_proc_thread.state = ACTIVE; + priv->tsi_reg_proc_thread.pid = + kernel_thread(da9052_tsi_reg_proc_thread, + priv, CLONE_KERNEL | SIGCHLD); + + init_completion(&priv->tsi_raw_proc_thread.notifier); + priv->tsi_raw_proc_thread.state = ACTIVE; + priv->tsi_raw_proc_thread.pid = + kernel_thread(da9052_tsi_raw_proc_thread, + priv, CLONE_KERNEL | SIGCHLD); + + ret = da9052_tsi_config_state(priv, DEFAULT_TSI_STATE); + if (ret) { + for (cnt = 0; cnt < NUM_INPUT_DEVS; cnt++) { + if (ts->input_devs[cnt] != NULL) + input_free_device(ts->input_devs[cnt]); + } + } + + return 0; +} + +u32 da9052_tsi_get_input_dev(u8 off) +{ + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + if (off > NUM_INPUT_DEVS-1) + return -EINVAL; + + return (u32)ts->input_devs[off]; +} + +static ssize_t da9052_tsi_config_pen_detect(struct da9052_ts_priv *priv, + u8 flag) +{ + u8 data; + u32 ret; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + da9052_lock(priv->da9052); + ret = read_da9052_reg(priv->da9052, DA9052_TSICONTA_REG); + if (ret < 0) { + DA9052_DEBUG("%s:", __FUNCTION__); + DA9052_DEBUG(" read_da9052_reg Failed\n" ); + da9052_unlock(priv->da9052); + return ret; + } + + if (flag == ENABLE) + data = set_bits((u8)ret, DA9052_TSICONTA_PENDETEN); + else if (flag == DISABLE) + data = clear_bits((u8)ret, DA9052_TSICONTA_PENDETEN); + else { + DA9052_DEBUG("%s:", __FUNCTION__); + DA9052_DEBUG(" Invalid flag passed \n" ); + da9052_unlock(priv->da9052); + return -EINVAL; + } + + ret = write_da9052_reg(priv->da9052, DA9052_TSICONTA_REG, data); + if (ret < 0) { + DA9052_DEBUG("%s:", __FUNCTION__); + DA9052_DEBUG(" write_da9052_reg Failed\n" ); + da9052_unlock(priv->da9052); + return ret; + } + da9052_unlock(priv->da9052); + + ts->tsi_conf.auto_cont.da9052_tsi_cont_a = data; + return 0; +} + +static ssize_t da9052_tsi_disable_irq(struct da9052_ts_priv *priv, + enum TSI_IRQ tsi_irq) +{ + u8 data = 0; + ssize_t ret =0; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + da9052_lock(priv->da9052); + ret = read_da9052_reg(priv->da9052, DA9052_IRQMASKB_REG); + if (ret < 0) { + DA9052_DEBUG("DA9052_TSI:da9052_tsi_disable_irq:"); + DA9052_DEBUG("read_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + data = ret; + switch (tsi_irq) { + case TSI_PEN_DWN: + data = mask_pendwn_irq(data); + break; + case TSI_DATA_RDY: + data = mask_tsi_rdy_irq(data); + break; + default: + DA9052_DEBUG("DA9052_TSI:da9052_tsi_disable_irq:"); + DA9052_DEBUG("Invalid IRQ passed \n" ); + da9052_unlock(priv->da9052); + return -EINVAL; + } + ret = write_da9052_reg(priv->da9052, DA9052_IRQMASKB_REG, data); + if (ret) { + DA9052_DEBUG("DA9052_TSI:da9052_tsi_disable_irq:"); + DA9052_DEBUG("write_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + da9052_unlock(priv->da9052); + switch (tsi_irq) { + case TSI_PEN_DWN: + ts->tsi_conf.tsi_pendown_irq_mask = SET; + break; + case TSI_DATA_RDY: + ts->tsi_conf.tsi_ready_irq_mask = SET; + break; + default: + return -EINVAL; + } + + return 0; + +} + +static ssize_t da9052_tsi_enable_irq(struct da9052_ts_priv *priv, + enum TSI_IRQ tsi_irq) +{ + u8 data =0; + ssize_t ret =0; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + da9052_lock(priv->da9052); + ret = read_da9052_reg(priv->da9052, DA9052_IRQMASKB_REG); + if (ret < 0) { + DA9052_DEBUG("DA9052_TSI:da9052_tsi_enable_irq:"); + DA9052_DEBUG("read_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + + data = ret; + switch (tsi_irq) { + case TSI_PEN_DWN: + data = unmask_pendwn_irq(data); + break; + case TSI_DATA_RDY: + data = unmask_tsi_rdy_irq(data); + break; + default: + DA9052_DEBUG("DA9052_TSI:da9052_tsi_enable_irq:"); + DA9052_DEBUG("Invalid IRQ passed \n" ); + da9052_unlock(priv->da9052); + return -EINVAL; + } + ret = write_da9052_reg(priv->da9052, DA9052_IRQMASKB_REG, data); + if (ret) { + DA9052_DEBUG("DA9052_TSI:da9052_tsi_enable_irq:"); + DA9052_DEBUG("write_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + da9052_unlock(priv->da9052); + switch (tsi_irq) { + case TSI_PEN_DWN: + ts->tsi_conf.tsi_pendown_irq_mask = RESET; + break; + case TSI_DATA_RDY: + ts->tsi_conf.tsi_ready_irq_mask = RESET; + break; + default: + return -EINVAL; + } + + return 0; + } + +static ssize_t da9052_tsi_config_gpio(struct da9052_ts_priv *priv) +{ + u8 idx = 0; + ssize_t ret = 0; + struct da9052_ssc_msg ssc_msg[priv->tsi_pdata->num_gpio_tsi_register]; + + ssc_msg[idx++].addr = DA9052_GPIO0203_REG; + ssc_msg[idx++].addr = DA9052_GPIO0405_REG; + ssc_msg[idx++].addr = DA9052_GPIO0607_REG; + + da9052_lock(priv->da9052); + ret = priv->da9052->read_many(priv->da9052, ssc_msg,idx); + if (ret) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG("da9052_ssc_read_many Failed\n" ); + da9052_unlock(priv->da9052); + return ret; + } + + idx = 0; + ssc_msg[idx].data = clear_bits(ssc_msg[idx].data, + DA9052_GPIO0203_GPIO3PIN); + idx++; + ssc_msg[idx].data = clear_bits(ssc_msg[idx].data, + (DA9052_GPIO0405_GPIO4PIN | DA9052_GPIO0405_GPIO5PIN)); + idx++; + ssc_msg[idx].data = clear_bits(ssc_msg[idx].data, + (DA9052_GPIO0607_GPIO6PIN | DA9052_GPIO0607_GPIO7PIN)); + idx++; + + ret = priv->da9052->write_many(priv->da9052, ssc_msg,idx); + if (ret) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG("da9052_ssc_read_many Failed\n" ); + da9052_unlock(priv->da9052); + return ret; + } + da9052_unlock(priv->da9052); + + return 0; +} + +s32 da9052_pm_configure_ldo(struct da9052_ts_priv *priv, + struct da9052_ldo_config ldo_config) +{ + struct da9052_ssc_msg msg; + u8 reg_num; + u8 ldo_volt; + u8 ldo_volt_bit = 0; + u8 ldo_conf_bit = 0; + u8 ldo_en_bit = 0; + s8 ldo_pd_bit = -1; + s32 ret = 0; + + if (validate_ldo9_mV(ldo_config.ldo_volt)) + return INVALID_LDO9_VOLT_VALUE; + + ldo_volt = ldo9_mV_to_reg(ldo_config.ldo_volt); + + reg_num = DA9052_LDO9_REG; + ldo_volt_bit = DA9052_LDO9_VLDO9; + ldo_conf_bit = DA9052_LDO9_LDO9CONF; + ldo_en_bit = DA9052_LDO9_LDO9EN; + + da9052_lock(priv->da9052); + + msg.addr = reg_num; + + ret = priv->da9052->read(priv->da9052, &msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EINVAL; + } + msg.data = ldo_volt | + (ldo_config.ldo_conf ? ldo_conf_bit : 0) | + (msg.data & ldo_en_bit); + + ret = priv->da9052->write(priv->da9052, &msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EINVAL; + } + + if (-1 != ldo_pd_bit) { + msg.addr = DA9052_PULLDOWN_REG; + ret = priv->da9052->read(priv->da9052, &msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EINVAL; + } + + msg.data = (ldo_config.ldo_pd ? + set_bits(msg.data, ldo_pd_bit) : + clear_bits(msg.data, ldo_pd_bit)); + + ret = priv->da9052->write(priv->da9052, &msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EINVAL; + } + + } + da9052_unlock(priv->da9052); + + return 0; +} + + +s32 da9052_pm_set_ldo(struct da9052_ts_priv *priv, u8 ldo_num, u8 flag) +{ + struct da9052_ssc_msg msg; + u8 reg_num = 0; + u8 value = 0; + s32 ret = 0; + + DA9052_DEBUG("I am in function: %s\n", __FUNCTION__); + + reg_num = DA9052_LDO9_REG; + value = DA9052_LDO9_LDO9EN; + da9052_lock(priv->da9052); + + msg.addr = reg_num; + + ret = priv->da9052->read(priv->da9052, &msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EINVAL; + } + + msg.data = flag ? + set_bits(msg.data, value) : + clear_bits(msg.data, value); + + ret = priv->da9052->write(priv->da9052, &msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EINVAL; + } + + da9052_unlock(priv->da9052); + + return 0; +} + +static ssize_t da9052_tsi_config_power_supply(struct da9052_ts_priv *priv, + u8 state) +{ + struct da9052_ldo_config ldo_config; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + if (state != ENABLE && state != DISABLE) { + DA9052_DEBUG("DA9052_TSI: %s: ", __FUNCTION__); + DA9052_DEBUG("Invalid state Passed\n" ); + return -EINVAL; + } + + ldo_config.ldo_volt = priv->tsi_pdata->tsi_supply_voltage; + ldo_config.ldo_num = priv->tsi_pdata->tsi_ref_source; + ldo_config.ldo_conf = RESET; + + if (da9052_pm_configure_ldo(priv, ldo_config)) + return -EINVAL; + + if (da9052_pm_set_ldo(priv, priv->tsi_pdata->tsi_ref_source, state)) + return -EINVAL; + + if (state == ENABLE) + ts->tsi_conf.ldo9_en = SET; + else + ts->tsi_conf.ldo9_en = RESET; + + return 0; +} + +static ssize_t da9052_tsi_config_auto_mode(struct da9052_ts_priv *priv, + u8 state) +{ + u8 data; + s32 ret = 0; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + if (state != ENABLE && state != DISABLE) + return -EINVAL; + + da9052_lock(priv->da9052); + + ret = read_da9052_reg(priv->da9052, DA9052_TSICONTA_REG); + if (ret < 0) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG("read_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + + data = (u8)ret; + + if (state == ENABLE) + data = set_auto_tsi_en(data); + else if (state == DISABLE) + data = reset_auto_tsi_en(data); + else { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG("Invalid Parameter Passed \n" ); + da9052_unlock(priv->da9052); + return -EINVAL; + } + + ret = write_da9052_reg(priv->da9052, DA9052_TSICONTA_REG, data); + if (ret) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG(" Failed to configure Auto TSI mode\n" ); + da9052_unlock(priv->da9052); + return ret; + } + da9052_unlock(priv->da9052); + ts->tsi_conf.auto_cont.da9052_tsi_cont_a = data; + return 0; +} + +static ssize_t da9052_tsi_config_manual_mode(struct da9052_ts_priv *priv, + u8 state) +{ + u8 data = 0; + ssize_t ret=0; + struct da9052_tsi_info *ts = get_tsi_drvdata(); + + if (state != ENABLE && state != DISABLE) { + DA9052_DEBUG("DA9052_TSI: %s: ", __FUNCTION__); + DA9052_DEBUG("Invalid state Passed\n" ); + return -EINVAL; + } + + da9052_lock(priv->da9052); + + ret = read_da9052_reg(priv->da9052, DA9052_TSICONTB_REG); + if (ret < 0) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG("read_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + + data = (u8)ret; + if (state == DISABLE) + data = disable_tsi_manual_mode(data); + else + data = enable_tsi_manual_mode(data); + + ret = write_da9052_reg(priv->da9052, DA9052_TSICONTB_REG, data); + if (ret) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG("write_da9052_reg Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + + if (state == DISABLE) + ts->tsi_conf.man_cont.tsi_cont_b.tsi_man = RESET; + else + ts->tsi_conf.man_cont.tsi_cont_b.tsi_man = SET; + + data = 0; + data = set_bits(data, DA9052_ADC_TSI); + + ret = write_da9052_reg(priv->da9052, DA9052_ADCMAN_REG, data); + if (ret) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG("ADC write Failed \n" ); + da9052_unlock(priv->da9052); + return ret; + } + da9052_unlock(priv->da9052); + + return 0; +} + +static u32 da9052_tsi_get_reg_data(struct da9052_ts_priv *priv) +{ + u32 free_cnt, copy_cnt, cnt; + + if (down_interruptible(&priv->tsi_reg_fifo.lock)) + return 0; + + copy_cnt = 0; + + if ((priv->tsi_reg_fifo.head - priv->tsi_reg_fifo.tail) > 1) { + free_cnt = get_reg_free_space_cnt(priv); + if (free_cnt > TSI_POLL_SAMPLE_CNT) + free_cnt = TSI_POLL_SAMPLE_CNT; + + cnt = da9052_tsi_get_rawdata( + &priv->tsi_reg_fifo.data[priv->tsi_reg_fifo.tail], + free_cnt); + + if (cnt > free_cnt) { + DA9052_DEBUG("EH copied more data"); + return -EINVAL; + } + + copy_cnt = cnt; + + while (cnt--) + incr_with_wrap_reg_fifo(priv->tsi_reg_fifo.tail); + + } else if ((priv->tsi_reg_fifo.head - priv->tsi_reg_fifo.tail) <= 0) { + + free_cnt = (TSI_REG_DATA_BUF_SIZE - priv->tsi_reg_fifo.tail); + if (free_cnt > TSI_POLL_SAMPLE_CNT) { + free_cnt = TSI_POLL_SAMPLE_CNT; + + cnt = da9052_tsi_get_rawdata( + &priv->tsi_reg_fifo.data[priv->tsi_reg_fifo.tail], + free_cnt); + if (cnt > free_cnt) { + DA9052_DEBUG("EH copied more data"); + return -EINVAL; + } + copy_cnt = cnt; + + while (cnt--) + incr_with_wrap_reg_fifo( + priv->tsi_reg_fifo.tail); + } else { + if (free_cnt) { + cnt = da9052_tsi_get_rawdata( + &priv-> + tsi_reg_fifo.data[priv-> + tsi_reg_fifo.tail], + free_cnt + ); + if (cnt > free_cnt) { + DA9052_DEBUG("EH copied more data"); + return -EINVAL; + } + copy_cnt = cnt; + while (cnt--) + incr_with_wrap_reg_fifo( + priv->tsi_reg_fifo.tail); + } + free_cnt = priv->tsi_reg_fifo.head; + if (free_cnt > TSI_POLL_SAMPLE_CNT - copy_cnt) + free_cnt = TSI_POLL_SAMPLE_CNT - copy_cnt; + if (free_cnt) { + cnt = da9052_tsi_get_rawdata( + &priv->tsi_reg_fifo.data[priv-> + tsi_reg_fifo.tail], free_cnt + ); + if (cnt > free_cnt) { + DA9052_DEBUG("EH copied more data"); + return -EINVAL; + } + + copy_cnt += cnt; + + while (cnt--) + incr_with_wrap_reg_fifo( + priv->tsi_reg_fifo.tail); + } + } + } else + copy_cnt = 0; + + up(&priv->tsi_reg_fifo.lock); + + return copy_cnt; +} + + +static ssize_t da9052_tsi_reg_proc_thread(void *ptr) +{ + u32 data_cnt; + ssize_t ret = 0; + struct da9052_tsi_info *ts; + struct da9052_ts_priv *priv = (struct da9052_ts_priv *)ptr; + + set_freezable(); + + while (priv->tsi_reg_proc_thread.state == ACTIVE) { + + try_to_freeze(); + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(priv-> + tsi_reg_data_poll_interval)); + + ts = get_tsi_drvdata(); + + if (!ts->pen_dwn_event) + continue; + + data_cnt = da9052_tsi_get_reg_data(priv); + + da9052_tsi_process_reg_data(priv); + + if (data_cnt) + ts->tsi_zero_data_cnt = 0; + else { + if ((++(ts->tsi_zero_data_cnt)) > + ts->tsi_penup_count) { + ts->pen_dwn_event = RESET; + da9052_tsi_penup_event(priv); + } + } + } + + complete_and_exit(&priv->tsi_reg_proc_thread.notifier, 0); + return 0; +} + + +static void da9052_tsi_penup_event(struct da9052_ts_priv *priv) +{ + + struct da9052_tsi_info *ts = get_tsi_drvdata(); + struct input_dev *ip_dev = + (struct input_dev *)da9052_tsi_get_input_dev( + (u8)TSI_INPUT_DEVICE_OFF); + + if (da9052_tsi_config_auto_mode(priv, DISABLE)) + goto exit; + ts->tsi_conf.auto_cont.tsi_cont_a.auto_tsi_en = RESET; + + if (da9052_tsi_config_power_supply(priv, ENABLE)) + goto exit; + + ts->tsi_conf.ldo9_en = RESET; + + if (da9052_tsi_enable_irq(priv, TSI_PEN_DWN)) + goto exit; + ts->tsi_conf.tsi_pendown_irq_mask = RESET; + tsi_reg.tsi_state = WAIT_FOR_PEN_DOWN; + + if (da9052_tsi_disable_irq(priv, TSI_DATA_RDY)) + goto exit; + + ts->tsi_zero_data_cnt = 0; + priv->early_data_flag = TRUE; + priv->debounce_over = FALSE; + priv->win_reference_valid = FALSE; + + printk(KERN_INFO "The raw data count is %d \n", priv->raw_data_cnt); + printk(KERN_INFO "The OS data count is %d \n", priv->os_data_cnt); + printk(KERN_INFO "PEN UP DECLARED \n"); + input_report_abs(ip_dev, BTN_TOUCH, 0); + input_sync(ip_dev); + priv->os_data_cnt = 0; + priv->raw_data_cnt = 0; + +exit: + clean_tsi_fifos(priv); + return; +} + +void da9052_tsi_pen_down_handler(struct da9052_eh_nb *eh_data, u32 event) +{ + ssize_t ret = 0; + struct da9052_ts_priv *priv = + container_of(eh_data, struct da9052_ts_priv, pd_nb); + struct da9052_tsi_info *ts = get_tsi_drvdata(); + struct input_dev *ip_dev = + (struct input_dev*)da9052_tsi_get_input_dev( + (u8)TSI_INPUT_DEVICE_OFF); + + if (tsi_reg.tsi_state != WAIT_FOR_PEN_DOWN) + return; + + tsi_reg.tsi_state = WAIT_FOR_SAMPLING; + + if (ts->tsi_conf.state != TSI_AUTO_MODE) { + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG(" Configure TSI to auto mode.\n" ); + DA9052_DEBUG("DA9052_TSI: %s:", __FUNCTION__); + DA9052_DEBUG(" Then call this API.\n" ); + goto fail; + } + + if (da9052_tsi_config_power_supply(priv, ENABLE)) + goto fail; + + if (da9052_tsi_disable_irq(priv, TSI_PEN_DWN)) + goto fail; + + if (da9052_tsi_config_auto_mode(priv, ENABLE)) + goto fail; + ts->tsi_conf.auto_cont.tsi_cont_a.auto_tsi_en = SET; + + if (da9052_tsi_enable_irq(priv, TSI_DATA_RDY)) + goto fail; + + input_sync(ip_dev); + + ts->tsi_rdy_event = (DA9052_EVENTB_ETSIREADY & (event>>8)); + ts->pen_dwn_event = (DA9052_EVENTB_EPENDOWN & (event>>8)); + + tsi_reg.tsi_state = SAMPLING_ACTIVE; + + goto success; + +fail: + if (ts->pd_reg_status) { + priv->da9052->unregister_event_notifier(priv->da9052, + &priv->pd_nb); + ts->pd_reg_status = RESET; + + priv->da9052->register_event_notifier(priv->da9052, + &priv->datardy_nb); + da9052_tsi_reg_pendwn_event(priv); + } + +success: + ret = 0; + printk(KERN_INFO "Exiting PEN DOWN HANDLER \n"); +} + +void da9052_tsi_data_ready_handler(struct da9052_eh_nb *eh_data, u32 event) +{ + struct da9052_ssc_msg tsi_data[4]; + s32 ret; + struct da9052_ts_priv *priv = + container_of(eh_data, struct da9052_ts_priv, datardy_nb); + + if (tsi_reg.tsi_state != SAMPLING_ACTIVE) + return; + + tsi_data[0].addr = DA9052_TSIXMSB_REG; + tsi_data[1].addr = DA9052_TSIYMSB_REG; + tsi_data[2].addr = DA9052_TSILSB_REG; + tsi_data[3].addr = DA9052_TSIZMSB_REG; + + tsi_data[0].data = 0; + tsi_data[1].data = 0; + tsi_data[2].data = 0; + tsi_data[3].data = 0; + + da9052_lock(priv->da9052); + + ret = priv->da9052->read_many(priv->da9052, tsi_data, 4); + if (ret) { + DA9052_DEBUG("Error in reading TSI data \n" ); + da9052_unlock(priv->da9052); + return; + } + da9052_unlock(priv->da9052); + +#if 1 + mutex_lock(&tsi_reg.tsi_fifo_lock); + + tsi_reg.tsi_fifo[tsi_reg.tsi_fifo_end].x_msb = tsi_data[0].data; + tsi_reg.tsi_fifo[tsi_reg.tsi_fifo_end].y_msb = tsi_data[1].data; + tsi_reg.tsi_fifo[tsi_reg.tsi_fifo_end].lsb = tsi_data[2].data; + tsi_reg.tsi_fifo[tsi_reg.tsi_fifo_end].z_msb = tsi_data[3].data; + incr_with_wrap(tsi_reg.tsi_fifo_end); + + if (tsi_reg.tsi_fifo_end == tsi_reg.tsi_fifo_start) + tsi_reg.tsi_fifo_start++; + + mutex_unlock(&tsi_reg.tsi_fifo_lock); +#endif +// printk(KERN_INFO "Exiting Data ready handler \n"); +} + +static s32 da9052_tsi_get_rawdata(struct da9052_tsi_reg *buf, u8 cnt) { + u32 data_cnt = 0; + u32 rem_data_cnt = 0; + + mutex_lock(&tsi_reg.tsi_fifo_lock); + + if (tsi_reg.tsi_fifo_start < tsi_reg.tsi_fifo_end) { + data_cnt = (tsi_reg.tsi_fifo_end - tsi_reg.tsi_fifo_start); + + if (cnt < data_cnt) + data_cnt = cnt; + + memcpy(buf, &tsi_reg.tsi_fifo[tsi_reg.tsi_fifo_start], + sizeof(struct da9052_tsi_reg) * data_cnt); + + tsi_reg.tsi_fifo_start += data_cnt; + + if (tsi_reg.tsi_fifo_start == tsi_reg.tsi_fifo_end) { + tsi_reg.tsi_fifo_start = 0; + tsi_reg.tsi_fifo_end = 0; + } + } else if (tsi_reg.tsi_fifo_start > tsi_reg.tsi_fifo_end) { + data_cnt = ((TSI_FIFO_SIZE - tsi_reg.tsi_fifo_start) + + tsi_reg.tsi_fifo_end); + + if (cnt < data_cnt) + data_cnt = cnt; + + if (data_cnt <= (TSI_FIFO_SIZE - tsi_reg.tsi_fifo_start)) { + memcpy(buf, &tsi_reg.tsi_fifo[tsi_reg.tsi_fifo_start], + sizeof(struct da9052_tsi_reg) * data_cnt); + + tsi_reg.tsi_fifo_start += data_cnt; + if (tsi_reg.tsi_fifo_start >= TSI_FIFO_SIZE) + tsi_reg.tsi_fifo_start = 0; + } else { + memcpy(buf, &tsi_reg.tsi_fifo[tsi_reg.tsi_fifo_start], + sizeof(struct da9052_tsi_reg) + * (TSI_FIFO_SIZE - tsi_reg.tsi_fifo_start)); + + rem_data_cnt = (data_cnt - + (TSI_FIFO_SIZE - tsi_reg.tsi_fifo_start)); + + memcpy(buf, &tsi_reg.tsi_fifo[0], + sizeof(struct da9052_tsi_reg) * rem_data_cnt); + + tsi_reg.tsi_fifo_start = rem_data_cnt; + } + + if (tsi_reg.tsi_fifo_start == tsi_reg.tsi_fifo_end) { + tsi_reg.tsi_fifo_start = 0; + tsi_reg.tsi_fifo_end = 0; + } + } else + data_cnt = 0; + + mutex_unlock(&tsi_reg.tsi_fifo_lock); + + return data_cnt; +} + +static ssize_t da9052_tsi_suspend(struct platform_device *dev, + pm_message_t state) +{ + printk(KERN_INFO "%s: called\n", __FUNCTION__); + return 0; +} + +static ssize_t da9052_tsi_resume(struct platform_device *dev) +{ + printk(KERN_INFO "%s: called\n", __FUNCTION__); + return 0; +} + +static s32 __devinit da9052_tsi_probe(struct platform_device *pdev) +{ + + struct da9052_ts_priv *priv; + struct da9052_tsi_platform_data *pdata = pdev->dev.platform_data; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->da9052 = dev_get_drvdata(pdev->dev.parent); + platform_set_drvdata(pdev, priv); + + priv->tsi_pdata = pdata; + + if (da9052_tsi_init_drv(priv)) + return -EFAULT; + + mutex_init(&tsi_reg.tsi_fifo_lock); + tsi_reg.tsi_state = WAIT_FOR_PEN_DOWN; + + printk(KERN_INFO "TSI Drv Successfully Inserted %s\n", + DA9052_TSI_DEVICE_NAME); + return 0; +} + +static int __devexit da9052_tsi_remove(struct platform_device *pdev) +{ + struct da9052_ts_priv *priv = platform_get_drvdata(pdev); + struct da9052_tsi_info *ts = get_tsi_drvdata(); + s32 ret = 0, i = 0; + + ret = da9052_tsi_config_state(priv, TSI_IDLE); + if (!ret) + return -EINVAL; + + if (ts->pd_reg_status) { + priv->da9052->unregister_event_notifier(priv->da9052, + &priv->pd_nb); + ts->pd_reg_status = RESET; + } + + if (ts->datardy_reg_status) { + priv->da9052->unregister_event_notifier(priv->da9052, + &priv->datardy_nb); + ts->datardy_reg_status = RESET; + } + + mutex_destroy(&tsi_reg.tsi_fifo_lock); + + priv->tsi_reg_proc_thread.state = INACTIVE; + wait_for_completion(&priv->tsi_reg_proc_thread.notifier); + + priv->tsi_raw_proc_thread.state = INACTIVE; + wait_for_completion(&priv->tsi_raw_proc_thread.notifier); + + for (i = 0; i < NUM_INPUT_DEVS; i++) { + input_unregister_device(ts->input_devs[i]); + } + + platform_set_drvdata(pdev, NULL); + DA9052_DEBUG(KERN_DEBUG "Removing %s \n", DA9052_TSI_DEVICE_NAME); + + return 0; +} + +static struct platform_driver da9052_tsi_driver = { + .probe = da9052_tsi_probe, + .remove = __devexit_p(da9052_tsi_remove), + .suspend = da9052_tsi_suspend, + .resume = da9052_tsi_resume, + .driver = { + .name = DA9052_TSI_DEVICE_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init da9052_tsi_init(void) +{ + printk("DA9052 TSI Device Driver, v1.0\n"); + return platform_driver_register(&da9052_tsi_driver); +} +module_init(da9052_tsi_init); + +static void __exit da9052_tsi_exit(void) +{ + printk(KERN_ERR "TSI Driver %s Successfully Removed \n", + DA9052_TSI_DEVICE_NAME); + return; + +} +module_exit(da9052_tsi_exit); + +MODULE_DESCRIPTION("Touchscreen driver for Dialog Semiconductor DA9052"); +MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/input/touchscreen/da9052_tsi_calibrate.c b/drivers/input/touchscreen/da9052_tsi_calibrate.c new file mode 100644 index 000000000000..4ffd0bbe7483 --- /dev/null +++ b/drivers/input/touchscreen/da9052_tsi_calibrate.c @@ -0,0 +1,107 @@ +/* + * da9052_tsi_calibrate.c -- TSI Calibration driver for Dialog DA9052 + * + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * Author: Dialog Semiconductor Ltd <dchen@diasemi.com> + * + * 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. + * + */ + +#include <linux/mfd/da9052/tsi.h> + +static struct Calib_xform_matrix_t xform = { + .An = DA9052_TSI_CALIB_AN, + .Bn = DA9052_TSI_CALIB_BN, + .Cn = DA9052_TSI_CALIB_CN, + .Dn = DA9052_TSI_CALIB_DN, + .En = DA9052_TSI_CALIB_EN, + .Fn = DA9052_TSI_CALIB_FN, + .Divider = DA9052_TSI_CALIB_DIVIDER +}; + +static struct calib_cfg_t calib = { + .calibrate_flag = TSI_USE_CALIBRATION, +}; + +struct calib_cfg_t *get_calib_config(void) +{ + return &calib; +} + +ssize_t da9052_tsi_set_calib_matrix(struct da9052_tsi_data *displayPtr, + struct da9052_tsi_data *screenPtr) +{ + + int retValue = SUCCESS ; + + xform.Divider = ((screenPtr[0].x - screenPtr[1].x) + * (screenPtr[1].y - screenPtr[2].y)) + - ((screenPtr[1].x - screenPtr[2].x) + * (screenPtr[0].y - screenPtr[1].y)); + + if (xform.Divider == 0) + retValue = -FAILURE; + else { + xform.An = ((displayPtr[0].x - displayPtr[1].x) + * (screenPtr[1].y - screenPtr[2].y)) + - ((displayPtr[1].x - displayPtr[2].x) + * (screenPtr[0].y - screenPtr[1].y)); + + xform.Bn = ((displayPtr[1].x - displayPtr[2].x) + * (screenPtr[0].x - screenPtr[1].x)) + - ((screenPtr[1].x - screenPtr[2].x) + * (displayPtr[0].x - displayPtr[1].x)); + + xform.Cn = (displayPtr[0].x * xform.Divider) + - (screenPtr[0].x * xform.An) + - (screenPtr[0].y * xform.Bn); + + xform.Dn = ((displayPtr[0].y - displayPtr[1].y) + * (screenPtr[1].y - screenPtr[2].y)) + - ((displayPtr[1].y - displayPtr[2].y) + * (screenPtr[0].y - screenPtr[1].y)); + + xform.En = ((displayPtr[1].y - displayPtr[2].y) + * (screenPtr[0].x - screenPtr[1].x)) + - ((screenPtr[1].x - screenPtr[2].x) + * (displayPtr[0].y - displayPtr[1].y)); + + xform.Fn = (displayPtr[0].y * xform.Divider) + - (screenPtr[0].x * xform.Dn) + - (screenPtr[0].y * xform.En); + } + + return retValue; +} + +ssize_t da9052_tsi_get_calib_display_point(struct da9052_tsi_data *displayPtr) +{ + int retValue = TRUE; + struct da9052_tsi_data screen_coord; + + screen_coord = *displayPtr; + if (xform.Divider != 0) { + displayPtr->x = ((xform.An * screen_coord.x) + + (xform.Bn * screen_coord.y) + + xform.Cn + ) / xform.Divider; + + displayPtr->y = ((xform.Dn * screen_coord.x) + + (xform.En * screen_coord.y) + + xform.Fn + ) / xform.Divider; + } else + retValue = FALSE; + +#if DA9052_TSI_CALIB_DATA_PROFILING + printk("C\tX\t%4d\tY\t%4d\n", + displayPtr->x, + displayPtr->y); +#endif + return retValue; +} diff --git a/drivers/input/touchscreen/da9052_tsi_filter.c b/drivers/input/touchscreen/da9052_tsi_filter.c new file mode 100644 index 000000000000..16467edf386f --- /dev/null +++ b/drivers/input/touchscreen/da9052_tsi_filter.c @@ -0,0 +1,489 @@ +/* + * da9052_tsi_filter.c -- TSI filter driver for Dialog DA9052 + * + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * Author: Dialog Semiconductor Ltd <dchen@diasemi.com> + * + * 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. + * + */ + +#include <linux/input.h> +#include <linux/freezer.h> +#include <linux/mfd/da9052/tsi.h> + +#define get_abs(x) (x < 0 ? ((-1) * x) : (x)) + +#define WITHIN_WINDOW(x, y) ((get_abs(x) <= TSI_X_WINDOW_SIZE) && \ + (get_abs(y) <= TSI_Y_WINDOW_SIZE)) + +#define FIRST_SAMPLE 0 + +#define incr_with_wrap_raw_fifo(x) \ + if (++x >= TSI_RAW_DATA_BUF_SIZE) \ + x = 0 + +#define decr_with_wrap_raw_fifo(x) \ + if (--x < 0) \ + x = (TSI_RAW_DATA_BUF_SIZE-1) + +static u32 get_raw_data_cnt(struct da9052_ts_priv *priv); +static void da9052_tsi_convert_reg_to_coord(struct da9052_ts_priv *priv, + struct da9052_tsi_data *raw_data); +static inline void clean_tsi_reg_fifo(struct da9052_ts_priv *priv); +static inline void clean_tsi_raw_fifo(struct da9052_ts_priv *priv); + +#if (ENABLE_AVERAGE_FILTER) +static void da9052_tsi_avrg_filter(struct da9052_ts_priv *priv, + struct da9052_tsi_data *tsi_avg_data); + +#endif +#if (ENABLE_TSI_DEBOUNCE) +static s32 da9052_tsi_calc_debounce_data(struct da9052_ts_priv *priv, + struct da9052_tsi_data *raw_data); + +#endif +# if (ENABLE_WINDOW_FILTER) +static s32 diff_within_window(struct da9052_tsi_data *prev_raw_data, + struct da9052_tsi_data *cur_raw_data); +#endif +static s32 da9052_tsi_window_filter(struct da9052_ts_priv *ts, + struct da9052_tsi_data *raw_data); + +void clean_tsi_fifos(struct da9052_ts_priv *priv) +{ + clean_tsi_raw_fifo(priv); + clean_tsi_reg_fifo(priv); +} + +void __init da9052_init_tsi_fifos(struct da9052_ts_priv *priv) +{ + init_MUTEX(&priv->tsi_raw_fifo.lock); + init_MUTEX(&priv->tsi_reg_fifo.lock); + + clean_tsi_raw_fifo(priv); + clean_tsi_reg_fifo(priv); +} + +u32 get_reg_data_cnt(struct da9052_ts_priv *priv) +{ + u8 reg_data_cnt; + + if (priv->tsi_reg_fifo.head <= priv->tsi_reg_fifo.tail) { + reg_data_cnt = (priv->tsi_reg_fifo.tail - + priv->tsi_reg_fifo.head); + } else { + reg_data_cnt = (priv->tsi_reg_fifo.tail + + (TSI_REG_DATA_BUF_SIZE - + priv->tsi_reg_fifo.head)); + } + + return reg_data_cnt; +} + +u32 get_reg_free_space_cnt(struct da9052_ts_priv *priv) +{ + u32 free_cnt; + + if (priv->tsi_reg_fifo.head <= priv->tsi_reg_fifo.tail) { + free_cnt = ((TSI_REG_DATA_BUF_SIZE - 1) - + (priv->tsi_reg_fifo.tail - priv->tsi_reg_fifo.head)); + } else + free_cnt = ((priv->tsi_reg_fifo.head - priv->tsi_reg_fifo.tail) + - 1); + + return free_cnt; +} + +void da9052_tsi_process_reg_data(struct da9052_ts_priv *priv) +{ + s32 ret; + struct da9052_tsi_data tmp_raw_data; + u32 reg_data_cnt; + + if (down_interruptible(&priv->tsi_reg_fifo.lock)) + return; + + reg_data_cnt = get_reg_data_cnt(priv); + + while (reg_data_cnt-- > 0) { + + ret = 0; + + if (get_raw_data_cnt(priv) >= (TSI_RAW_DATA_BUF_SIZE - 1)) { + DA9052_DEBUG("%s: RAW data FIFO is full\n", + __FUNCTION__); + break; + } + + da9052_tsi_convert_reg_to_coord(priv, &tmp_raw_data); + + if ((tmp_raw_data.x < TS_X_MIN) || + (tmp_raw_data.x > TS_X_MAX) || + (tmp_raw_data.y < TS_Y_MIN) || + (tmp_raw_data.y > TS_Y_MAX)) { + DA9052_DEBUG("%s: ", __FUNCTION__); + DA9052_DEBUG("sample beyond touchscreen panel "); + DA9052_DEBUG("dimensions\n"); + continue; + } + +#if (ENABLE_TSI_DEBOUNCE) + if (debounce_over == FALSE) { + ret = + da9052_tsi_calc_debounce_data(priv, &tmp_raw_data); + if (ret != SUCCESS) + continue; + } +#endif + +# if (ENABLE_WINDOW_FILTER) + ret = da9052_tsi_window_filter(priv, &tmp_raw_data); + if (ret != SUCCESS) + continue; +#endif + priv->early_data_flag = FALSE; + + if (down_interruptible(&priv->tsi_raw_fifo.lock)) { + DA9052_DEBUG("%s: Failed to ", __FUNCTION__); + DA9052_DEBUG("acquire RAW FIFO Lock!\n"); + + up(&priv->tsi_reg_fifo.lock); + return; + } + + priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.tail] = tmp_raw_data; + incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.tail); + + up(&priv->tsi_raw_fifo.lock); + } + + + up(&priv->tsi_reg_fifo.lock); + + return; +} + +static u32 get_raw_data_cnt(struct da9052_ts_priv *priv) +{ + u32 raw_data_cnt; + + if (priv->tsi_raw_fifo.head <= priv->tsi_raw_fifo.tail) + raw_data_cnt = + (priv->tsi_raw_fifo.tail - priv->tsi_raw_fifo.head); + else + raw_data_cnt = + (priv->tsi_raw_fifo.tail + (TSI_RAW_DATA_BUF_SIZE - + priv->tsi_raw_fifo.head)); + + return raw_data_cnt; +} + +static void da9052_tsi_convert_reg_to_coord(struct da9052_ts_priv *priv, + struct da9052_tsi_data *raw_data) +{ + + struct da9052_tsi_reg *src; + struct da9052_tsi_data *dst = raw_data; + + src = &priv->tsi_reg_fifo.data[priv->tsi_reg_fifo.head]; + + dst->x = (src->x_msb << X_MSB_SHIFT); + dst->x |= (src->lsb & X_LSB_MASK) >> X_LSB_SHIFT; + + dst->y = (src->y_msb << Y_MSB_SHIFT); + dst->y |= (src->lsb & Y_LSB_MASK) >> Y_LSB_SHIFT; + + dst->z = (src->z_msb << Z_MSB_SHIFT); + dst->z |= (src->lsb & Z_LSB_MASK) >> Z_LSB_SHIFT; + +#if DA9052_TSI_RAW_DATA_PROFILING + printk("R\tX\t%4d\tY\t%4d\tZ\t%4d\n", + (u16)dst->x, + (u16)dst->y, + (u16)dst->z); +#endif + priv->raw_data_cnt++; + incr_with_wrap_reg_fifo(priv->tsi_reg_fifo.head); +} + +#if (ENABLE_AVERAGE_FILTER) +static void da9052_tsi_avrg_filter(struct da9052_ts_priv *priv, + struct da9052_tsi_data *tsi_avg_data) +{ + u8 cnt; + + if (down_interruptible(&priv->tsi_raw_fifo.lock)) { + printk("%s: No RAW Lock !\n", __FUNCTION__); + return; + } + + (*tsi_avg_data) = priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head]; + incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.head); + + for (cnt = 2; cnt <= TSI_AVERAGE_FILTER_SIZE; cnt++) { + + tsi_avg_data->x += + priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head].x; + tsi_avg_data->y += + priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head].y; + tsi_avg_data->z += + priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head].z; + + incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.head); + } + + up(&priv->tsi_raw_fifo.lock); + + tsi_avg_data->x /= TSI_AVERAGE_FILTER_SIZE; + tsi_avg_data->y /= TSI_AVERAGE_FILTER_SIZE; + tsi_avg_data->z /= TSI_AVERAGE_FILTER_SIZE; + +#if DA9052_TSI_AVG_FLT_DATA_PROFILING + printk("A\tX\t%4d\tY\t%4d\tZ\t%4d\n", + (u16)tsi_avg_data->x, + (u16)tsi_avg_data->y, + (u16)tsi_avg_data->z); +#endif + + return; +} +#endif + +s32 da9052_tsi_raw_proc_thread(void *ptr) +{ + struct da9052_tsi_data coord; + u8 calib_ok, range_ok; + struct calib_cfg_t *tsi_calib = get_calib_config(); + struct input_dev *ip_dev = (struct input_dev *) + da9052_tsi_get_input_dev( + (u8)TSI_INPUT_DEVICE_OFF); + struct da9052_ts_priv *priv = (struct da9052_ts_priv *)ptr; + + set_freezable(); + + while (priv->tsi_raw_proc_thread.state == ACTIVE) { + + try_to_freeze(); + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout( + msecs_to_jiffies(priv->tsi_raw_data_poll_interval)); + + if (priv->early_data_flag || (get_raw_data_cnt(priv) == 0)) + continue; + + calib_ok = TRUE; + range_ok = TRUE; + +#if (ENABLE_AVERAGE_FILTER) + + if (get_raw_data_cnt(priv) < TSI_AVERAGE_FILTER_SIZE) + continue; + + da9052_tsi_avrg_filter(priv, &coord); + +#else + + if (down_interruptible(&priv->tsi_raw_fifo.lock)) + continue; + + coord = priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head]; + incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.head); + + up(&priv->tsi_raw_fifo.lock); + +#endif + + if (tsi_calib->calibrate_flag) { + calib_ok = da9052_tsi_get_calib_display_point(&coord); + + if ((coord.x < DISPLAY_X_MIN) || + (coord.x > DISPLAY_X_MAX) || + (coord.y < DISPLAY_Y_MIN) || + (coord.y > DISPLAY_Y_MAX)) + range_ok = FALSE; + } + + if (calib_ok && range_ok) { + input_report_abs(ip_dev, BTN_TOUCH, 1); + input_report_abs(ip_dev, ABS_X, coord.x); + input_report_abs(ip_dev, ABS_Y, coord.y); + input_sync(ip_dev); + + priv->os_data_cnt++; + +#if DA9052_TSI_OS_DATA_PROFILING + printk("O\tX\t%4d\tY\t%4d\tZ\t%4d\n", (u16)coord.x, + (u16)coord.y, (u16)coord.z); +#endif + } else { + if (!calib_ok) { + DA9052_DEBUG("%s: ", __FUNCTION__); + DA9052_DEBUG("calibration Failed\n"); + } + if (!range_ok) { + DA9052_DEBUG("%s: ", __FUNCTION__); + DA9052_DEBUG("sample beyond display "); + DA9052_DEBUG("panel dimension\n"); + } + } + } + priv->tsi_raw_proc_thread.thread_task = NULL; + complete_and_exit(&priv->tsi_raw_proc_thread.notifier, 0); + return 0; + +} + +#if (ENABLE_TSI_DEBOUNCE) +static s32 da9052_tsi_calc_debounce_data(struct da9052_tsi_data *raw_data) +{ +#if (TSI_DEBOUNCE_DATA_CNT) + u8 cnt; + struct da9052_tsi_data temp = {.x = 0, .y = 0, .z = 0}; + + priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.tail] = (*raw_data); + incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.tail); + + if (get_raw_data_cnt(priv) <= TSI_DEBOUNCE_DATA_CNT) + return -FAILURE; + + for (cnt = 1; cnt <= TSI_DEBOUNCE_DATA_CNT; cnt++) { + temp.x += priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head].x; + temp.y += priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head].y; + temp.z += priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head].z; + + incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.head); + } + + temp.x /= TSI_DEBOUNCE_DATA_CNT; + temp.y /= TSI_DEBOUNCE_DATA_CNT; + temp.z /= TSI_DEBOUNCE_DATA_CNT; + + priv->tsi_raw_fifo.tail = priv->tsi_raw_fifo.head; + priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.tail] = temp; + incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.tail); + +#if DA9052_TSI_PRINT_DEBOUNCED_DATA + printk("D: X: %d Y: %d Z: %d\n", + (u16)temp.x, (u16)temp.y, (u16)temp.z); +#endif + +#endif + priv->debounce_over = TRUE; + + return 0; +} +#endif + +# if (ENABLE_WINDOW_FILTER) +static s32 diff_within_window(struct da9052_tsi_data *prev_raw_data, + struct da9052_tsi_data *cur_raw_data) +{ + s32 ret = -EINVAL; + s32 x, y; + x = ((cur_raw_data->x) - (prev_raw_data->x)); + y = ((cur_raw_data->y) - (prev_raw_data->y)); + + if (WITHIN_WINDOW(x, y)) { + +#if DA9052_TSI_WIN_FLT_DATA_PROFILING + printk("W\tX\t%4d\tY\t%4d\tZ\t%4d\n", + (u16)cur_raw_data->x, + (u16)cur_raw_data->y, + (u16)cur_raw_data->z); +#endif + ret = 0; + } + return ret; +} + +static s32 da9052_tsi_window_filter(struct da9052_ts_priv *priv, + struct da9052_tsi_data *raw_data) +{ + u8 ref_found; + u32 cur, next; + s32 ret = -EINVAL; + static struct da9052_tsi_data prev_raw_data; + + if (priv->win_reference_valid == TRUE) { + +#if DA9052_TSI_PRINT_PREVIOUS_DATA + printk("P: X: %d Y: %d Z: %d\n", + (u16)prev_raw_data.x, (u16)prev_raw_data.y, + (u16)prev_raw_data.z); +#endif + ret = diff_within_window(&prev_raw_data, raw_data); + if (!ret) + prev_raw_data = (*raw_data); + } else { + priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.tail] = (*raw_data); + incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.tail); + + if (get_raw_data_cnt(priv) == SAMPLE_CNT_FOR_WIN_REF) { + + ref_found = FALSE; + + next = cur = priv->tsi_raw_fifo.head; + incr_with_wrap_raw_fifo(next); + + while (next <= priv->tsi_raw_fifo.tail) { + ret = diff_within_window( + &priv->tsi_raw_fifo.data[cur], + &priv->tsi_raw_fifo.data[next] + ); + if (ret == SUCCESS) { + ref_found = TRUE; + break; + } + incr_with_wrap_raw_fifo(cur); + incr_with_wrap_raw_fifo(next); + + } + + if (ref_found == FALSE) + priv->tsi_raw_fifo.tail = + priv->tsi_raw_fifo.head; + else { + prev_raw_data = priv->tsi_raw_fifo.data[cur]; + + prev_raw_data.x += + priv->tsi_raw_fifo.data[next].x; + prev_raw_data.y += + priv->tsi_raw_fifo.data[next].y; + prev_raw_data.z += + priv->tsi_raw_fifo.data[next].z; + + prev_raw_data.x = prev_raw_data.x / 2; + prev_raw_data.y = prev_raw_data.y / 2; + prev_raw_data.z = prev_raw_data.z / 2; + + (*raw_data) = prev_raw_data; + + priv->tsi_raw_fifo.tail = + priv->tsi_raw_fifo.head; + + priv->win_reference_valid = TRUE; + ret = SUCCESS; + } + } + } + return ret; +} +#endif +static inline void clean_tsi_reg_fifo(struct da9052_ts_priv *priv) +{ + priv->tsi_reg_fifo.head = FIRST_SAMPLE; + priv->tsi_reg_fifo.tail = FIRST_SAMPLE; +} + +static inline void clean_tsi_raw_fifo(struct da9052_ts_priv *priv) +{ + priv->tsi_raw_fifo.head = FIRST_SAMPLE; + priv->tsi_raw_fifo.tail = FIRST_SAMPLE; +} + diff --git a/drivers/leds/leds-da9052.c b/drivers/leds/leds-da9052.c new file mode 100644 index 000000000000..187d4d2372e0 --- /dev/null +++ b/drivers/leds/leds-da9052.c @@ -0,0 +1,307 @@ +/* + * leds-da9052.c -- LED Driver for Dialog DA9052 + * + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * Author: Dialog Semiconductor Ltd <dchen@diasemi.com> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/workqueue.h> +#include <linux/slab.h> + +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/led.h> + +#define DRIVER_NAME "da9052-leds" + +#define DA9052_LED4_PRESENT 1 +#define DA9052_LED5_PRESENT 1 + + +struct da9052_led_data { + struct led_classdev cdev; + struct work_struct work; + struct da9052 *da9052; + int id; + int new_brightness; + int is_led4_present; + int is_led5_present; +}; + +#define GPIO14_PIN 2 /* GPO Open Drain */ +#define GPIO14_TYPE 0 /* VDD_IO1 */ +#define GPIO14_MODE 1 /* Output High */ + +#define GPIO15_PIN 2 /* GPO Open Drain */ +#define GPIO15_TYPE 0 /* VDD_IO1 */ +#define GPIO15_MODE 1 /* Output High */ + +#define MAXIMUM_PWM 95 +#define MASK_GPIO14 0x0F +#define MASK_GPIO15 0xF0 +#define GPIO15_PIN_BIT_POSITION 4 + +static void da9052_led_work(struct work_struct *work) +{ + struct da9052_led_data *led = container_of(work, + struct da9052_led_data, work); + int reg = 0; + int led_dim_bit = 0; + struct da9052_ssc_msg msg; + int ret = 0; + + switch (led->id) { + case DA9052_LED_4: + reg = DA9052_LED4CONT_REG; + led_dim_bit = DA9052_LED4CONT_LED4DIM; + break; + case DA9052_LED_5: + reg = DA9052_LED5CONT_REG; + led_dim_bit = DA9052_LED5CONT_LED5DIM; + break; + } + + if (led->new_brightness > MAXIMUM_PWM) + led->new_brightness = MAXIMUM_PWM; + + /* Always enable DIM feature + * This feature can be disabled if required + */ + msg.addr = reg; + msg.data = led->new_brightness | led_dim_bit; + da9052_lock(led->da9052); + ret = led->da9052->write(led->da9052, &msg); + if (ret) { + da9052_unlock(led->da9052); + return; + } + da9052_unlock(led->da9052); +} + +static void da9052_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct da9052_led_data *led; + + led = container_of(led_cdev, struct da9052_led_data, cdev); + led->new_brightness = value; + schedule_work(&led->work); +} + +static int __devinit da9052_led_setup(struct da9052_led_data *led) +{ + int reg = 0; + int ret = 0; + + struct da9052_ssc_msg msg; + + switch (led->id) { + case DA9052_LED_4: + reg = DA9052_LED4CONT_REG; + break; + case DA9052_LED_5: + reg = DA9052_LED5CONT_REG; + break; + } + + msg.addr = reg; + msg.data = 0; + + da9052_lock(led->da9052); + ret = led->da9052->write(led->da9052, &msg); + if (ret) { + da9052_unlock(led->da9052); + return ret; + } + da9052_unlock(led->da9052); + return ret; +} + +static int da9052_leds_prepare(struct da9052_led_data *led) +{ + int ret = 0; + struct da9052_ssc_msg msg; + + da9052_lock(led->da9052); + + if (1 == led->is_led4_present) { + msg.addr = DA9052_GPIO1415_REG; + msg.data = 0; + + ret = led->da9052->read(led->da9052, &msg); + if (ret) + goto out; + msg.data = msg.data & ~(MASK_GPIO14); + msg.data = msg.data | ( + GPIO14_PIN | + (GPIO14_TYPE ? DA9052_GPIO1415_GPIO14TYPE : 0) | + (GPIO14_MODE ? DA9052_GPIO1415_GPIO14MODE : 0)); + + ret = led->da9052->write(led->da9052, &msg); + if (ret) + goto out; + } + + if (1 == led->is_led5_present) { + msg.addr = DA9052_GPIO1415_REG; + msg.data = 0; + + ret = led->da9052->read(led->da9052, &msg); + if (ret) + goto out; + msg.data = msg.data & ~(MASK_GPIO15); + msg.data = msg.data | + (((GPIO15_PIN << GPIO15_PIN_BIT_POSITION) | + (GPIO15_TYPE ? DA9052_GPIO1415_GPIO15TYPE : 0) | + (GPIO15_MODE ? DA9052_GPIO1415_GPIO15MODE : 0)) + ); + ret = led->da9052->write(led->da9052, &msg); + if (ret) + goto out; + } + + da9052_unlock(led->da9052); + return ret; +out: + da9052_unlock(led->da9052); + return ret; +} + +static int __devinit da9052_led_probe(struct platform_device *pdev) +{ + struct da9052_leds_platform_data *pdata = (pdev->dev.platform_data); + struct da9052_led_platform_data *led_cur; + struct da9052_led_data *led, *led_dat; + int ret, i; + int init_led = 0; + + if (pdata->num_leds < 1 || pdata->num_leds > DA9052_LED_MAX) { + dev_err(&pdev->dev, "Invalid led count %d\n", pdata->num_leds); + return -EINVAL; + } + + led = kzalloc(sizeof(*led) * pdata->num_leds, GFP_KERNEL); + if (led == NULL) { + dev_err(&pdev->dev, "failed to alloc memory\n"); + return -ENOMEM; + } + + led->is_led4_present = DA9052_LED4_PRESENT; + led->is_led5_present = DA9052_LED5_PRESENT; + + for (i = 0; i < pdata->num_leds; i++) { + led_dat = &led[i]; + led_cur = &pdata->led[i]; + if (led_cur->id < 0) { + dev_err(&pdev->dev, "invalid id %d\n", led_cur->id); + ret = -EINVAL; + goto err_register; + } + + if (init_led & (1 << led_cur->id)) { + dev_err(&pdev->dev, "led %d already initialized\n", + led_cur->id); + ret = -EINVAL; + goto err_register; + } + + init_led |= 1 << led_cur->id; + + led_dat->cdev.name = led_cur->name; + // led_dat->cdev.default_trigger = led_cur[i]->default_trigger; + led_dat->cdev.brightness_set = da9052_led_set; + led_dat->cdev.brightness = LED_OFF; + led_dat->id = led_cur->id; + led_dat->da9052 = dev_get_drvdata(pdev->dev.parent); + + INIT_WORK(&led_dat->work, da9052_led_work); + + ret = led_classdev_register(pdev->dev.parent, &led_dat->cdev); + if (ret) { + dev_err(&pdev->dev, "failed to register led %d\n", + led_dat->id); + goto err_register; + + } + ret = da9052_led_setup(led_dat); + if (ret) { + dev_err(&pdev->dev, "unable to init led %d\n", + led_dat->id); + i++; + goto err_register; + } + } + ret = da9052_leds_prepare(led); + if (ret) { + dev_err(&pdev->dev, "unable to init led driver\n"); + goto err_free; + } + + platform_set_drvdata(pdev, led); + return 0; + +err_register: + for (i = i - 1; i >= 0; i--) { + led_classdev_unregister(&led[i].cdev); + cancel_work_sync(&led[i].work); + } + +err_free: + kfree(led); + return ret; +} + +static int __devexit da9052_led_remove(struct platform_device *pdev) +{ + struct da9052_leds_platform_data *pdata = + (struct da9052_leds_platform_data *)pdev->dev.platform_data; + struct da9052_led_data *led = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < pdata->num_leds; i++) { + da9052_led_setup(&led[i]); + led_classdev_unregister(&led[i].cdev); + cancel_work_sync(&led[i].work); + } + + kfree(led); + return 0; +} + +static struct platform_driver da9052_led_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = da9052_led_probe, + .remove = __devexit_p(da9052_led_remove), +}; + +static int __init da9052_led_init(void) +{ + return platform_driver_register(&da9052_led_driver); +} +module_init(da9052_led_init); + +static void __exit da9052_led_exit(void) +{ + platform_driver_unregister(&da9052_led_driver); +} +module_exit(da9052_led_exit); + +MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com> "); +MODULE_DESCRIPTION("LED driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/mfd/da9052-core.c b/drivers/mfd/da9052-core.c new file mode 100644 index 000000000000..b19eeafbc857 --- /dev/null +++ b/drivers/mfd/da9052-core.c @@ -0,0 +1,533 @@ +/* + * da9052-core.c -- Device access for Dialog DA9052 + * + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * Author: Dialog Semiconductor Ltd <dchen@diasemi.com> + * + * 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. + * + */ + +#include <linux/device.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/irq.h> +#include <linux/list.h> +#include <linux/mfd/core.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/adc.h> + +#define SUCCESS 0 +#define FAILURE 1 + +struct da9052_eh_nb eve_nb_array[EVE_CNT]; +static struct da9052_ssc_ops ssc_ops; +struct mutex manconv_lock; +static struct semaphore eve_nb_array_lock; + +void da9052_lock(struct da9052 *da9052) +{ + mutex_lock(&da9052->ssc_lock); +} +EXPORT_SYMBOL(da9052_lock); + +void da9052_unlock(struct da9052 *da9052) +{ + mutex_unlock(&da9052->ssc_lock); +} +EXPORT_SYMBOL(da9052_unlock); + +int da9052_ssc_write(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg) +{ + int ret = 0; + + /* Reg address should be a valid address on PAGE0 or PAGE1 */ + if ((sscmsg->addr < DA9052_PAGE0_REG_START) || + (sscmsg->addr > DA9052_PAGE1_REG_END) || + ((sscmsg->addr > DA9052_PAGE0_REG_END) && + (sscmsg->addr < DA9052_PAGE1_REG_START))) + return INVALID_REGISTER; + + ret = ssc_ops.write(da9052, sscmsg); + + /* Update local cache if required */ + if (!ret) { + /* Check if this register is Non-volatile*/ + if (da9052->ssc_cache[sscmsg->addr].type != VOLATILE) { + /* Update value */ + da9052->ssc_cache[sscmsg->addr].val = sscmsg->data; + /* Make this cache entry valid */ + da9052->ssc_cache[sscmsg->addr].status = VALID; + } + } + + return ret; +} + +int da9052_ssc_read(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg) +{ + int ret = 0; + + /* Reg addr should be a valid address on PAGE0 or PAGE1 */ + if ((sscmsg->addr < DA9052_PAGE0_REG_START) || + (sscmsg->addr > DA9052_PAGE1_REG_END) || + ((sscmsg->addr > DA9052_PAGE0_REG_END) && + (sscmsg->addr < DA9052_PAGE1_REG_START))) + return INVALID_REGISTER; + + /* + * Check if this is a Non-volatile register, if yes then return value - + * from cache instead of actual reading from hardware. Before reading - + * cache entry, make sure that the entry is valid + */ + /* The read request is for Non-volatile register */ + /* Check if we have valid cached value for this */ + if (da9052->ssc_cache[sscmsg->addr].status == VALID) { + /* We have valid cached value, copy this value */ + sscmsg->data = da9052->ssc_cache[sscmsg->addr].val; + + return 0; + } + + ret = ssc_ops.read(da9052, sscmsg); + + /* Update local cache if required */ + if (!ret) { + /* Check if this register is Non-volatile*/ + if (da9052->ssc_cache[sscmsg->addr].type != VOLATILE) { + /* Update value */ + da9052->ssc_cache[sscmsg->addr].val = sscmsg->data; + /* Make this cache entry valid */ + da9052->ssc_cache[sscmsg->addr].status = VALID; + } + } + + return ret; +} + +int da9052_ssc_write_many(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg, + int msg_no) +{ + int ret = 0; + int cnt = 0; + + /* Check request size */ + if (msg_no > MAX_READ_WRITE_CNT) + return -EIO; + + ret = ssc_ops.write_many(da9052, sscmsg, msg_no); + /* Update local cache, if required */ + for (cnt = 0; cnt < msg_no; cnt++) { + /* Check if this register is Non-volatile*/ + if (da9052->ssc_cache[sscmsg[cnt].addr].type != VOLATILE) { + /* Update value */ + da9052->ssc_cache[sscmsg[cnt].addr].val = + sscmsg[cnt].data; + /* Make this cache entry valid */ + da9052->ssc_cache[sscmsg[cnt].addr].status = VALID; + } + } + return ret; +} + +int da9052_ssc_read_many(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg, + int msg_no) +{ + int ret = 0; + int cnt = 0; + + /* Check request size */ + if (msg_no > MAX_READ_WRITE_CNT) + return -EIO; + + ret = ssc_ops.read_many(da9052, sscmsg, msg_no); + /* Update local cache, if required */ + for (cnt = 0; cnt < msg_no; cnt++) { + /* Check if this register is Non-volatile*/ + if (da9052->ssc_cache[sscmsg[cnt].addr].type + != VOLATILE) { + /* Update value */ + da9052->ssc_cache[sscmsg[cnt].addr].val = + sscmsg[cnt].data; + /* Make this cache entry valid */ + da9052->ssc_cache[sscmsg[cnt].addr].status = VALID; + } + } + return ret; +} + +static irqreturn_t da9052_eh_isr(int irq, void *dev_id) +{ + struct da9052 *da9052 = dev_id; + /* Schedule work to be done */ + schedule_work(&da9052->eh_isr_work); + /* Disable IRQ */ + disable_irq_nosync(DA9052_IRQ); + return IRQ_HANDLED; +} + +int eh_register_nb(struct da9052 *da9052, struct da9052_eh_nb *nb) +{ + + if (nb == NULL) { + printk(KERN_INFO "EH REGISTER FUNCTION FAILED\n"); + return -EINVAL; + } + + if (nb->eve_type >= EVE_CNT) { + printk(KERN_INFO "Invalid DA9052 Event Type\n"); + return -EINVAL; + } + + /* Initialize list head inside notifier block */ + INIT_LIST_HEAD(&nb->nb_list); + + /* Acquire NB array lock */ + if (down_interruptible(&eve_nb_array_lock)) + return -EAGAIN; + + /* Add passed NB to corresponding EVENT list */ + list_add_tail(&nb->nb_list, &(eve_nb_array[nb->eve_type].nb_list)); + + /* Release NB array lock */ + up(&eve_nb_array_lock); + + return 0; +} + +int eh_unregister_nb(struct da9052 *da9052, struct da9052_eh_nb *nb) +{ + + if (nb == NULL) + return -EINVAL; + + /* Acquire nb array lock */ + if (down_interruptible(&eve_nb_array_lock)) + return -EAGAIN; + + /* Remove passed NB from list */ + list_del_init(&(nb->nb_list)); + + /* Release NB array lock */ + up(&eve_nb_array_lock); + + return 0; +} + +static int process_events(struct da9052 *da9052, int events_sts) +{ + + int cnt = 0; + int tmp_events_sts = 0; + unsigned char event = 0; + + struct list_head *ptr; + struct da9052_eh_nb *nb_ptr; + + /* Now we have retrieved all events, process them one by one */ + for (cnt = 0; cnt < EVE_CNT; cnt++) { + /* + * Starting with highest priority event, + * traverse through all event + */ + tmp_events_sts = events_sts; + + /* Find the event associated with higher priority */ + event = cnt; + + /* Check if interrupt is received for this event */ + if (!((tmp_events_sts >> cnt) & 0x1)) + /* Event bit is not set for this event */ + /* Move to next event */ + continue; + + if (event == PEN_DOWN_EVE) { + if (list_empty(&(eve_nb_array[event].nb_list))) + continue; + } + + /* Event bit is set, execute all registered call backs */ + if (down_interruptible(&eve_nb_array_lock)){ + printk(KERN_CRIT "Can't acquire eve_nb_array_lock \n"); + return -EIO; + } + + list_for_each(ptr, &(eve_nb_array[event].nb_list)) { + /* + * nb_ptr will point to the structure in which + * nb_list is embedded + */ + nb_ptr = list_entry(ptr, struct da9052_eh_nb, nb_list); + nb_ptr->call_back(nb_ptr, events_sts); + } + up(&eve_nb_array_lock); + } + return 0; +} + +void eh_workqueue_isr(struct work_struct *work) +{ + struct da9052 *da9052 = + container_of(work, struct da9052, eh_isr_work); + + struct da9052_ssc_msg eve_data[4]; + struct da9052_ssc_msg eve_mask_data[4]; + int events_sts, ret; + u32 mask; + unsigned char cnt = 0; + + /* nIRQ is asserted, read event registeres to know what happened */ + events_sts = 0; + mask = 0; + + /* Prepare ssc message to read all four event registers */ + for (cnt = 0; cnt < DA9052_EVE_REGISTERS; cnt++) { + eve_data[cnt].addr = (DA9052_EVENTA_REG + cnt); + eve_data[cnt].data = 0; + } + + /* Prepare ssc message to read all four event registers */ + for (cnt = 0; cnt < DA9052_EVE_REGISTERS; cnt++) { + eve_mask_data[cnt].addr = (DA9052_IRQMASKA_REG + cnt); + eve_mask_data[cnt].data = 0; + } + + /* Now read all event and mask registers */ + da9052_lock(da9052); + + ret = da9052_ssc_read_many(da9052,eve_data, DA9052_EVE_REGISTERS); + if (ret) { + enable_irq(DA9052_IRQ); + da9052_unlock(da9052); + return; + } + + ret = da9052_ssc_read_many(da9052,eve_mask_data, DA9052_EVE_REGISTERS); + if (ret) { + enable_irq(DA9052_IRQ); + da9052_unlock(da9052); + return; + } + /* Collect all events */ + for (cnt = 0; cnt < DA9052_EVE_REGISTERS; cnt++) + events_sts |= (eve_data[cnt].data << (DA9052_EVE_REGISTER_SIZE + * cnt)); + /* Collect all mask */ + for (cnt = 0; cnt < DA9052_EVE_REGISTERS; cnt++) + mask |= (eve_mask_data[cnt].data << (DA9052_EVE_REGISTER_SIZE + * cnt)); + events_sts &= ~mask; + da9052_unlock(da9052); + + /* Check if we really got any event */ + if (events_sts == 0) { + enable_irq(DA9052_IRQ); + da9052_unlock(da9052); + return; + } + + /* Process all events occurred */ + process_events(da9052, events_sts); + + da9052_lock(da9052); + /* Now clear EVENT registers */ + for (cnt = 0; cnt < 4; cnt++) { + if (eve_data[cnt].data) { + ret = da9052_ssc_write(da9052, &eve_data[cnt]); + if (ret) { + enable_irq(DA9052_IRQ); + da9052_unlock(da9052); + return; + } + } + } + da9052_unlock(da9052); + + /* + * This delay is necessary to avoid hardware fake interrupts + * from DA9052. + */ +#if defined CONFIG_PMIC_DA9052 || defined CONFIG_PMIC_DA9053AA + udelay(50); +#endif + /* Enable HOST interrupt */ + enable_irq(DA9052_IRQ); +} + +static void da9052_eh_restore_irq(void) +{ + /* Put your platform and board specific code here */ + free_irq(DA9052_IRQ, NULL); +} + +static int da9052_add_subdevice_pdata(struct da9052 *da9052, + const char *name, void *pdata, size_t pdata_size) +{ + struct mfd_cell cell = { + .name = name, + .platform_data = pdata, + .data_size = pdata_size, + }; + return mfd_add_devices(da9052->dev, -1, &cell, 1, NULL, 0); +} + +static int da9052_add_subdevice(struct da9052 *da9052, const char *name) +{ + return da9052_add_subdevice_pdata(da9052, name, NULL, 0); +} + +static int add_da9052_devices(struct da9052 *da9052) +{ + s32 ret = 0; + struct da9052_platform_data *pdata = da9052->dev->platform_data; + struct da9052_leds_platform_data leds_data = { + .num_leds = pdata->led_data->num_leds, + .led = pdata->led_data->led, + }; + struct da9052_regulator_platform_data regulator_pdata = { + .regulators = pdata->regulators, + }; + + struct da9052_tsi_platform_data tsi_data = *(pdata->tsi_data); + + if (pdata && pdata->init) { + ret = pdata->init(da9052); + if (ret != 0) + return ret; + } else + printk(KERN_CRIT "No platform initialisation supplied \n"); + + ret = da9052_add_subdevice(da9052, "da9052-rtc"); + if (ret) + return ret; + ret = da9052_add_subdevice(da9052, "da9052-onkey"); + if (ret) + return ret; + + ret = da9052_add_subdevice(da9052, "WLED-1"); + if (ret) + return ret; + + ret = da9052_add_subdevice(da9052, "WLED-2"); + if (ret) + return ret; + + ret = da9052_add_subdevice(da9052, "WLED-3"); + if (ret) + return ret; + + ret = da9052_add_subdevice(da9052, "da9052-adc"); + if (ret) + return ret; + + ret = da9052_add_subdevice(da9052, "da9052-wdt"); + if (ret) + return ret; + + ret = da9052_add_subdevice_pdata(da9052, "da9052-gpio", + pdata, sizeof(*pdata)); + if (ret) + return ret; + + ret = da9052_add_subdevice_pdata(da9052, "da9052-leds", + &leds_data, sizeof(leds_data)); + if (ret) + return ret; + + ret = da9052_add_subdevice_pdata(da9052, "da9052-regulator", + ®ulator_pdata, sizeof(regulator_pdata)); + if (ret) + return ret; + + ret = da9052_add_subdevice_pdata(da9052, "da9052-tsi", + &tsi_data, sizeof(tsi_data)); + if (ret) + return ret; + + ret = da9052_add_subdevice(da9052, "da9052-bat"); + if (ret) + return ret; + + return ret; +} + +int da9052_ssc_init(struct da9052 *da9052) +{ + int cnt; + struct da9052_platform_data *pdata; + + /* Initialize eve_nb_array */ + for (cnt = 0; cnt < EVE_CNT; cnt++) + INIT_LIST_HEAD(&(eve_nb_array[cnt].nb_list)); + + /* Initialize mutex required for ADC Manual read */ + mutex_init(&manconv_lock); + + /* Initialize NB array lock */ + init_MUTEX(&eve_nb_array_lock); + + /* Assign the read-write function pointers */ + da9052->read = da9052_ssc_read; + da9052->write = da9052_ssc_write; + da9052->read_many = da9052_ssc_read_many; + da9052->write_many = da9052_ssc_write_many; + + if (SPI == da9052->connecting_device && ssc_ops.write == NULL) { + /* Assign the read/write pointers to SPI/read/write */ + ssc_ops.write = da9052_spi_write; + ssc_ops.read = da9052_spi_read; + ssc_ops.write_many = da9052_spi_write_many; + ssc_ops.read_many = da9052_spi_read_many; + } + else if (I2C == da9052->connecting_device && ssc_ops.write == NULL) { + /* Assign the read/write pointers to SPI/read/write */ + ssc_ops.write = da9052_i2c_write; + ssc_ops.read = da9052_i2c_read; + ssc_ops.write_many = da9052_i2c_write_many; + ssc_ops.read_many = da9052_i2c_read_many; + } else + return -1; + /* Assign the EH notifier block register/de-register functions */ + da9052->register_event_notifier = eh_register_nb; + da9052->unregister_event_notifier = eh_unregister_nb; + + /* Initialize ssc lock */ + mutex_init(&da9052->ssc_lock); + + pdata = da9052->dev->platform_data; + add_da9052_devices(da9052); + + INIT_WORK(&da9052->eh_isr_work, eh_workqueue_isr); + + if (request_irq(DA9052_IRQ, da9052_eh_isr, IRQ_TYPE_LEVEL_LOW, + DA9052_EH_DEVICE_NAME, da9052)) + return -EIO; + + return 0; +} + +void da9052_ssc_exit(struct da9052 *da9052) +{ + printk(KERN_INFO "DA9052: Unregistering SSC device.\n"); + mutex_destroy(&manconv_lock); + /* Restore IRQ line */ + da9052_eh_restore_irq(); + free_irq(DA9052_IRQ, NULL); + mutex_destroy(&da9052->ssc_lock); + mutex_destroy(&da9052->eve_nb_lock); + return; +} + +MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com>"); +MODULE_DESCRIPTION("DA9052 MFD Core"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DA9052_SSC_DEVICE_NAME); diff --git a/drivers/mfd/da9052-i2c.c b/drivers/mfd/da9052-i2c.c new file mode 100644 index 000000000000..5828b6db4585 --- /dev/null +++ b/drivers/mfd/da9052-i2c.c @@ -0,0 +1,376 @@ +/* + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * 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. + * + * da9052-i2c.c: I2C SSC (Synchronous Serial Communication) driver for DA9052 + */ + +#include <linux/device.h> +#include <linux/mfd/core.h> +#include <linux/i2c.h> +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> + +static struct da9052 *da9052_i2c; + +#define I2C_CONNECTED 0 + +static int da9052_i2c_is_connected(void) +{ + + struct da9052_ssc_msg msg; + + //printk("Entered da9052_i2c_is_connected.............\n"); + + msg.addr = DA9052_INTERFACE_REG; + + /* Test spi connectivity by performing read of the GPIO_0-1 register */ + if ( 0 != da9052_i2c_read(da9052_i2c, &msg)) { + printk("da9052_i2c_is_connected - i2c read failed.............\n"); + return -1; + } + else { + printk("da9052_i2c_is_connected - i2c read success..............\n"); + return 0; + } + +} + +static int __devinit da9052_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter; + // printk("\n\tEntered da9052_i2c_is_probe.............\n"); + + da9052_i2c = kzalloc(sizeof(struct da9052), GFP_KERNEL); + + if (!da9052_i2c) + return -ENOMEM; + + /* Get the bus driver handler */ + adapter = to_i2c_adapter(client->dev.parent); + + /* Check i2c bus driver supports byte data transfer */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_info(&client->dev,\ + "Error in %s:i2c_check_functionality\n", __func__); + return -ENODEV;; + } + + /* Store handle to i2c client */ + da9052_i2c->i2c_client = client; + + da9052_i2c->dev = &client->dev; + + /* Initialize i2c data structure here*/ + da9052_i2c->adapter = adapter; + + /* host i2c driver looks only first 7 bits for the slave address */ + da9052_i2c->slave_addr = DA9052_I2C_ADDR >> 1; + + /* Store the i2c client data */ + i2c_set_clientdata(client, da9052_i2c); + + /* Validate I2C connectivity */ + if ( I2C_CONNECTED == da9052_i2c_is_connected()) { + /* I2C is connected */ + da9052_i2c->connecting_device = I2C; + if( 0!= da9052_ssc_init(da9052_i2c) ) + return -ENODEV; + } + else { + return -ENODEV; + } + + //printk("Exiting da9052_i2c_probe.....\n"); + + return 0; +} + +static int da9052_i2c_remove(struct i2c_client *client) +{ + + struct da9052 *da9052 = i2c_get_clientdata(client); + + mfd_remove_devices(da9052->dev); + kfree(da9052); + return 0; +} + +int da9052_i2c_write(struct da9052 *da9052, struct da9052_ssc_msg *msg) +{ + struct i2c_msg i2cmsg; + unsigned char buf[2] = {0}; + int ret = 0; + + /* Copy the ssc msg to local character buffer */ + buf[0] = msg->addr; + buf[1] = msg->data; + + /*Construct a i2c msg for a da9052 driver ssc message request */ + i2cmsg.addr = da9052->slave_addr; + i2cmsg.len = 2; + i2cmsg.buf = buf; + + /* To write the data on I2C set flag to zero */ + i2cmsg.flags = 0; + + /* Start the i2c transfer by calling host i2c driver function */ + ret = i2c_transfer(da9052->adapter, &i2cmsg, 1); + + if (ret < 0) { + dev_info(&da9052->i2c_client->dev,\ + "_%s:master_xfer Failed!!\n", __func__); + return ret; + } + + return 0; +} + +int da9052_i2c_read(struct da9052 *da9052, struct da9052_ssc_msg *msg) +{ + + /*Get the da9052_i2c client details*/ + unsigned char buf[2] = {0, 0}; + struct i2c_msg i2cmsg[2]; + int ret = 0; + + /* Copy SSC Msg to local character buffer */ + buf[0] = msg->addr; + + /*Construct a i2c msg for a da9052 driver ssc message request */ + i2cmsg[0].addr = da9052->slave_addr ; + i2cmsg[0].len = 1; + i2cmsg[0].buf = &buf[0]; + + /*To write the data on I2C set flag to zero */ + i2cmsg[0].flags = 0; + + /* Read the data from da9052*/ + /*Construct a i2c msg for a da9052 driver ssc message request */ + i2cmsg[1].addr = da9052->slave_addr ; + i2cmsg[1].len = 1; + i2cmsg[1].buf = &buf[1]; + + /*To read the data on I2C set flag to I2C_M_RD */ + i2cmsg[1].flags = I2C_M_RD; + + /* Start the i2c transfer by calling host i2c driver function */ + ret = i2c_transfer(da9052->adapter, i2cmsg, 2); + if (ret < 0) { + dev_info(&da9052->i2c_client->dev,\ + "2 - %s:master_xfer Failed!!\n", __func__); + return ret; + } + + msg->data = *i2cmsg[1].buf; + + return 0; +} + +int da9052_i2c_write_many(struct da9052 *da9052, + struct da9052_ssc_msg *sscmsg, int msg_no) +{ + + struct i2c_msg i2cmsg; + unsigned char data_buf[MAX_READ_WRITE_CNT+1]; + struct da9052_ssc_msg ctrlb_msg; + struct da9052_ssc_msg *msg_queue = sscmsg; + int ret = 0; + /* Flag to check if requested registers are contiguous */ + unsigned char cont_data = 1; + unsigned char cnt = 0; + + /* Check if requested registers are contiguous */ + for (cnt = 1; cnt < msg_no; cnt++) { + if ((msg_queue[cnt].addr - msg_queue[cnt-1].addr) != 1) { + /* Difference is not 1, i.e. non-contiguous registers */ + cont_data = 0; + break; + } + } + + if (cont_data == 0) { + /* Requested registers are non-contiguous */ + for (cnt = 0; cnt < msg_no; cnt++) { + ret = da9052->write(da9052, &msg_queue[cnt]); + if (ret != 0) + return ret; + } + return 0; + } + /* + * Requested registers are contiguous + * or PAGE WRITE sequence of I2C transactions is as below + * (slave_addr + reg_addr + data_1 + data_2 + ...) + * First read current WRITE MODE via CONTROL_B register of DA9052 + */ + ctrlb_msg.addr = DA9052_CONTROLB_REG; + ctrlb_msg.data = 0x0; + ret = da9052->read(da9052, &ctrlb_msg); + + if (ret != 0) + return ret; + + /* Check if PAGE WRITE mode is set */ + if (ctrlb_msg.data & DA9052_CONTROLB_WRITEMODE) { + /* REPEAT WRITE mode is configured */ + /* Now set DA9052 into PAGE WRITE mode */ + ctrlb_msg.data &= ~DA9052_CONTROLB_WRITEMODE; + ret = da9052->write(da9052, &ctrlb_msg); + + if (ret != 0) + return ret; + } + + /* Put first register address */ + data_buf[0] = msg_queue[0].addr; + + for (cnt = 0; cnt < msg_no; cnt++) + data_buf[cnt+1] = msg_queue[cnt].data; + + /* Construct a i2c msg for PAGE WRITE */ + i2cmsg.addr = da9052->slave_addr ; + /* First register address + all data*/ + i2cmsg.len = (msg_no + 1); + i2cmsg.buf = data_buf; + + /*To write the data on I2C set flag to zero */ + i2cmsg.flags = 0; + + /* Start the i2c transfer by calling host i2c driver function */ + ret = i2c_transfer(da9052->adapter, &i2cmsg, 1); + if (ret < 0) { + dev_info(&da9052->i2c_client->dev,\ + "1 - i2c_transfer function falied in [%s]!!!\n", __func__); + return ret; + } + + return 0; +} + +int da9052_i2c_read_many(struct da9052 *da9052, + struct da9052_ssc_msg *sscmsg, int msg_no) +{ + + struct i2c_msg i2cmsg; + unsigned char data_buf[MAX_READ_WRITE_CNT]; + struct da9052_ssc_msg *msg_queue = sscmsg; + int ret = 0; + /* Flag to check if requested registers are contiguous */ + unsigned char cont_data = 1; + unsigned char cnt = 0; + + /* Check if requested registers are contiguous */ + for (cnt = 1; cnt < msg_no; cnt++) { + if ((msg_queue[cnt].addr - msg_queue[cnt-1].addr) != 1) { + /* Difference is not 1, i.e. non-contiguous registers */ + cont_data = 0; + break; + } + } + + if (cont_data == 0) { + /* Requested registers are non-contiguous */ + for (cnt = 0; cnt < msg_no; cnt++) { + ret = da9052->read(da9052, &msg_queue[cnt]); + if (ret != 0) { + dev_info(&da9052->i2c_client->dev,\ + "Error in %s", __func__); + return ret; + } + } + return 0; + } + + /* + * We want to perform PAGE READ via I2C + * For PAGE READ sequence of I2C transactions is as below + * (slave_addr + reg_addr) + (slave_addr + data_1 + data_2 + ...) + */ + /* Copy address of first register */ + data_buf[0] = msg_queue[0].addr; + + /* Construct a i2c msg for first transaction of PAGE READ i.e. write */ + i2cmsg.addr = da9052->slave_addr ; + i2cmsg.len = 1; + i2cmsg.buf = data_buf; + + /*To write the data on I2C set flag to zero */ + i2cmsg.flags = 0; + + /* Start the i2c transfer by calling host i2c driver function */ + ret = i2c_transfer(da9052->adapter, &i2cmsg, 1); + if (ret < 0) { + dev_info(&da9052->i2c_client->dev,\ + "1 - i2c_transfer function falied in [%s]!!!\n", __func__); + return ret; + } + + /* Now Read the data from da9052 */ + /* Construct a i2c msg for second transaction of PAGE READ i.e. read */ + i2cmsg.addr = da9052->slave_addr ; + i2cmsg.len = msg_no; + i2cmsg.buf = data_buf; + + /*To read the data on I2C set flag to I2C_M_RD */ + i2cmsg.flags = I2C_M_RD; + + /* Start the i2c transfer by calling host i2c driver function */ + ret = i2c_transfer(da9052->adapter, + &i2cmsg, 1); + if (ret < 0) { + dev_info(&da9052->i2c_client->dev,\ + "2 - i2c_transfer function falied in [%s]!!!\n", __func__); + return ret; + } + + /* Gather READ data */ + for (cnt = 0; cnt < msg_no; cnt++) + sscmsg[cnt].data = data_buf[cnt]; + + return 0; +} + +static struct i2c_device_id da9052_ssc_id[] = { + { DA9052_SSC_I2C_DEVICE_NAME, 0}, + {} +}; + +static struct i2c_driver da9052_i2c_driver = { + .driver = { + .name = DA9052_SSC_I2C_DEVICE_NAME, + .owner = THIS_MODULE, + }, + .probe = da9052_i2c_probe, + .remove = da9052_i2c_remove, + .id_table = da9052_ssc_id, +}; + +static int __init da9052_i2c_init(void) +{ + int ret = 0; + // printk("\n\nEntered da9052_i2c_init................\n\n"); + ret = i2c_add_driver(&da9052_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Unable to register %s\n", DA9052_SSC_I2C_DEVICE_NAME); + return ret; + } + return 0; +} +module_init(da9052_i2c_init); + +static void __exit da9052_i2c_exit(void) +{ + i2c_del_driver(&da9052_i2c_driver); +} +module_exit(da9052_i2c_exit); + +MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com>"); +MODULE_DESCRIPTION("I2C driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DA9052_SSC_I2C_DEVICE_NAME); diff --git a/drivers/mfd/da9052-spi.c b/drivers/mfd/da9052-spi.c new file mode 100644 index 000000000000..9dd90c2d19f9 --- /dev/null +++ b/drivers/mfd/da9052-spi.c @@ -0,0 +1,399 @@ +/* + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * 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. + * + * da9052-spi.c: SPI SSC (Synchronous Serial Communication) driver for DA9052 + */ + +#include <linux/device.h> +#include <linux/mfd/core.h> +#include <linux/spi/spi.h> +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> + + +struct da9052 *da9052_spi; + +#define SPI_CONNECTED 0 + +static int da9052_spi_is_connected(void) +{ + + struct da9052_ssc_msg msg; + + //printk("Entered da9052_spi_is_connected.............\n"); + + msg.addr = DA9052_INTERFACE_REG; + + /* Test spi connectivity by performing read of the GPIO_0-1 register and then verify the read value*/ + if ( 0 != da9052_spi_read(da9052_spi, &msg)) { + printk("da9052_spi_is_connected - spi read failed.............\n"); + return -1; + } + else if( 0x88 != msg.data ){ + printk("da9052_spi_is_connected - spi read failed. Msg data =%x ..............\n",msg.data); + return -1; + } + + return 0; + +} + +static int da9052_spi_probe(struct spi_device *spi) +{ + //printk("\n\tEntered da9052_spi_probe.....\n"); + + da9052_spi = kzalloc(sizeof(struct da9052), GFP_KERNEL); + + if (!da9052_spi) + return -ENOMEM; + + + spi->mode = SPI_MODE_0 | SPI_CPOL; + spi->bits_per_word = 8; + spi_setup(spi); + + da9052_spi->dev = &spi->dev; + + da9052_spi->spi_dev = spi; + + /* + * Allocate memory for RX/TX bufferes used in single register read/write + */ + da9052_spi->spi_rx_buf = kmalloc(2, GFP_KERNEL | GFP_DMA); + if (!da9052_spi->spi_rx_buf) + return -ENOMEM; + + da9052_spi->spi_tx_buf = kmalloc(2, GFP_KERNEL | GFP_DMA); + if (!da9052_spi->spi_tx_buf) + return -ENOMEM; + + da9052_spi->spi_active_page = PAGECON_0; + da9052_spi->rw_pol = 1; + + + dev_set_drvdata(&spi->dev, da9052_spi); + + + /* Validate SPI connectivity */ + if ( SPI_CONNECTED == da9052_spi_is_connected()) { + /* SPI is connected */ + da9052_spi->connecting_device = SPI; + if( 0 != da9052_ssc_init(da9052_spi) ) + return -ENODEV; + } + else { + return -ENODEV; + } + + //printk("Exiting da9052_spi_probe.....\n"); + + return 0; +} + +static int da9052_spi_remove(struct spi_device *spi) +{ + struct da9052 *da9052 = dev_get_drvdata(&spi->dev); + + printk("Entered da9052_spi_remove()\n"); + if(SPI == da9052->connecting_device ) { + da9052_ssc_exit(da9052); + } + mfd_remove_devices(&spi->dev); + kfree(da9052->spi_rx_buf); + kfree(da9052->spi_tx_buf); + kfree(da9052); + return 0; +} + +static struct spi_driver da9052_spi_driver = { + .driver = { + .name = DA9052_SSC_SPI_DEVICE_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = da9052_spi_probe, + .remove = __devexit_p(da9052_spi_remove), +}; + + +static int da9052_spi_set_page(struct da9052 *da9052, unsigned char page) +{ + + struct da9052_ssc_msg sscmsg; + struct spi_message message; + struct spi_transfer xfer; + int ret = 0; + + printk("Entered da9052_spi_set_page.....\n"); + if ((page != PAGECON_0) && ((page != PAGECON_128))) + return INVALID_PAGE; + + /* Current configuration is PAGE-0 and write request for PAGE-1 */ + /* set register address */ + sscmsg.addr = DA9052_PAGECON0_REG; + /* set value */ + sscmsg.data = page; + + /* Check value of R/W_POL bit of INTERFACE register */ + if (!da9052->rw_pol) { + /* We need to set 0th bit for write operation */ + sscmsg.addr = ((sscmsg.addr << 1) | RW_POL); + } else { + /* We need to reset 0th bit for write operation */ + sscmsg.addr = (sscmsg.addr << 1); + } + + /* SMDK-6410 host SPI driver specific stuff */ + + /* Build our spi message */ + printk("da9052_spi_set_page - Calling spi_message_init.....\n"); + spi_message_init(&message); + memset(&xfer, 0, sizeof(xfer)); + + xfer.len = 2; + xfer.tx_buf = da9052->spi_tx_buf; + xfer.rx_buf = da9052->spi_rx_buf; + + da9052->spi_tx_buf[0] = sscmsg.addr; + da9052->spi_tx_buf[1] = sscmsg.data; + + printk("da9052_spi_set_page - Calling spi_message_add_tail.....\n"); + spi_message_add_tail(&xfer, &message); + + /* Now, do the i/o */ + printk("da9052_spi_set_page - Calling spi_sync.....\n"); + ret = spi_sync(da9052->spi_dev, &message); + + if (ret == 0) { + /* Active Page set successfully */ + da9052->spi_active_page = page; + return 0; + } else { + /* Error in setting Active Page */ + return ret; + } + + return 0; +} + +int da9052_spi_write(struct da9052 *da9052, struct da9052_ssc_msg *msg) +{ + + struct spi_message message; + struct spi_transfer xfer; + int ret; + + /* + * We need a seperate copy of da9052_ssc_msg so that caller's + * copy remains intact + */ + struct da9052_ssc_msg sscmsg; + + /* Copy callers data in to our local copy */ + sscmsg.addr = msg->addr; + sscmsg.data = msg->data; + + if ((sscmsg.addr > PAGE_0_END) && + (da9052->spi_active_page == PAGECON_0)) { + /* + * Current configuration is PAGE-0 and write request + * for PAGE-1 + */ + da9052_spi_set_page(da9052, PAGECON_128); + /* Set register address accordindly */ + sscmsg.addr = (sscmsg.addr - PAGE_1_START); + } else if ((sscmsg.addr < PAGE_1_START) && + (da9052->spi_active_page == PAGECON_128)) { + /* + * Current configuration is PAGE-1 and write request + * for PAGE-0 + */ + da9052_spi_set_page(da9052, PAGECON_0); + } else if (sscmsg.addr > PAGE_0_END) { + /* + * Current configuration is PAGE-1 and write request + * for PAGE-1. Just need to adjust register address + */ + sscmsg.addr = (sscmsg.addr - PAGE_1_START); + } + + /* Check value of R/W_POL bit of INTERFACE register */ + if (!da9052->rw_pol) { + /* We need to set 0th bit for write operation */ + sscmsg.addr = ((sscmsg.addr << 1) | RW_POL); + } else { + /* We need to reset 0th bit for write operation */ + sscmsg.addr = (sscmsg.addr << 1); + } + + /* SMDK-6410 host SPI driver specific stuff */ + + /* Build our spi message */ + spi_message_init(&message); + memset(&xfer, 0, sizeof(xfer)); + + xfer.len = 2; + xfer.tx_buf = da9052->spi_tx_buf; + xfer.rx_buf = da9052->spi_rx_buf; + + da9052->spi_tx_buf[0] = sscmsg.addr; + da9052->spi_tx_buf[1] = sscmsg.data; + + spi_message_add_tail(&xfer, &message); + + /* Now, do the i/o */ + ret = spi_sync(da9052->spi_dev, &message); + + return ret; +} + +int da9052_spi_write_many(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg, + int msg_no) +{ + int cnt,ret=0; + + for(cnt = 0; cnt < msg_no; cnt++, sscmsg++) { + ret = da9052_ssc_write(da9052,sscmsg); + if(ret != 0) + { + printk("Error in %s", __FUNCTION__); + return -EIO; + } + } + + return 0; +} + +int da9052_spi_read(struct da9052 *da9052, struct da9052_ssc_msg *msg) +{ + + struct spi_message message; + struct spi_transfer xfer; + int ret; + + /* + * We need a seperate copy of da9052_ssc_msg so that + * caller's copy remains intact + */ + struct da9052_ssc_msg sscmsg; + + + /* Copy callers data in to our local copy */ + sscmsg.addr = msg->addr; + sscmsg.data = msg->data; + + if ((sscmsg.addr > PAGE_0_END) && + (da9052->spi_active_page == PAGECON_0)) { + /* + * Current configuration is PAGE-0 and + * read request for PAGE-1 + */ + printk("da9052_spi_read - if PAGECON_128.....\n"); + da9052_spi_set_page(da9052, PAGECON_128); + /* Set register address accordindly */ + sscmsg.addr = (sscmsg.addr - PAGE_1_START); + } else if ((sscmsg.addr < PAGE_1_START) && + (da9052->spi_active_page == PAGECON_128)) { + /* + * Current configuration is PAGE-1 and + * write request for PAGE-0 + */ + printk("da9052_spi_read - if PAGECON_0.....\n"); + da9052_spi_set_page(da9052, PAGECON_0); + } else if (sscmsg.addr > PAGE_0_END) { + /* + * Current configuration is PAGE-1 and write + * request for PAGE-1 + * Just need to adjust register address + */ + sscmsg.addr = (sscmsg.addr - PAGE_1_START); + } + + /* Check value of R/W_POL bit of INTERFACE register */ + if (da9052->rw_pol) { + /* We need to set 0th bit for read operation */ + sscmsg.addr = ((sscmsg.addr << 1) | RW_POL); + } else { + /* We need to reset 0th bit for write operation */ + sscmsg.addr = (sscmsg.addr << 1); + } + + /* SMDK-6410 host SPI driver specific stuff */ + + /* Build our spi message */ + spi_message_init(&message); + memset(&xfer, 0, sizeof(xfer)); + + xfer.len = 2; + xfer.tx_buf = da9052->spi_tx_buf; + xfer.rx_buf = da9052->spi_rx_buf; + + da9052->spi_tx_buf[0] = sscmsg.addr; + da9052->spi_tx_buf[1] = 0xff; + + da9052->spi_rx_buf[0] = 0; + da9052->spi_rx_buf[1] = 0; + + spi_message_add_tail(&xfer, &message); + + /* Now, do the i/o */ + ret = spi_sync(da9052->spi_dev, &message); + + if (ret == 0) { + /* Update read value in callers copy */ + msg->data = da9052->spi_rx_buf[1]; + return 0; + } else { + return ret; + } + + + return 0; +} + +int da9052_spi_read_many(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg, + int msg_no) +{ + int cnt,ret=0; + + for(cnt = 0; cnt < msg_no; cnt++, sscmsg++) { + ret = da9052_ssc_read(da9052,sscmsg); + if(ret != 0) + { + printk("Error in %s", __FUNCTION__); + return -EIO; + } + } + + return 0; +} + +static int __init da9052_spi_init(void) +{ + int ret = 0; + //printk("Entered da9052_spi_init.....\n"); + ret = spi_register_driver(&da9052_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Unable to register %s\n", DA9052_SSC_SPI_DEVICE_NAME); + return ret; + } + return 0; +} +module_init(da9052_spi_init); + +static void __exit da9052_spi_exit(void) +{ + spi_unregister_driver(&da9052_spi_driver); +} + +module_exit(da9052_spi_exit); + +MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com>"); +MODULE_DESCRIPTION("SPI driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DA9052_SSC_SPI_DEVICE_NAME); diff --git a/drivers/power/da9052-battery.c b/drivers/power/da9052-battery.c new file mode 100644 index 000000000000..85561b463df5 --- /dev/null +++ b/drivers/power/da9052-battery.c @@ -0,0 +1,844 @@ +/* + * da9052-battery.c -- Batttery Driver for Dialog DA9052 + * + * Copyright(c) 2009 Dialog Semiconductor Ltd. + + * Author: Dialog Semiconductor Ltd <dchen@diasemi.com> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/uaccess.h> +#include <linux/jiffies.h> +#include <linux/power_supply.h> +#include <linux/platform_device.h> +#include <linux/freezer.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/bat.h> +#include <linux/mfd/da9052/adc.h> + +#define DA9052_BAT_DEVICE_NAME "da9052-bat" + +static const char __initdata banner[] = KERN_INFO "DA9052 BAT, (c) \ +2009 Dialog semiconductor Ltd.\n"; + +static struct da9052_bat_hysteresis bat_hysteresis; +static struct da9052_bat_event_registration event_status; + + +static u16 array_hys_batvoltage[2]; +static u16 bat_volt_arr[3]; +static u8 hys_flag = FALSE; + +static int da9052_read(struct da9052 *da9052, u8 reg_address, u8 *reg_data) +{ + struct da9052_ssc_msg msg; + int ret; + + msg.addr = reg_address; + msg.data = 0; + + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret) + goto ssc_comm_err; + da9052_unlock(da9052); + + *reg_data = msg.data; + return 0; +ssc_comm_err: + da9052_unlock(da9052); + return ret; +} + +static s32 da9052_adc_read_ich(struct da9052 *da9052, u16 *data) +{ + struct da9052_ssc_msg msg; + da9052_lock(da9052); + /* Read charging conversion register */ + msg.addr = DA9052_ICHGAV_REG; + msg.data = 0; + if (da9052->read(da9052, &msg)) { + da9052_unlock(da9052); + return DA9052_SSC_FAIL; + } + da9052_unlock(da9052); + + *data = (u16)msg.data; + DA9052_DEBUG( + "In function: %s, ICHGAV_REG value read (1)= 0x%X \n", + __func__, msg.data); + return SUCCESS; +} + + +static s32 da9052_adc_read_tbat(struct da9052 *da9052, u16 *data) +{ + s32 ret; + u8 reg_data; + + ret = da9052_read(da9052, DA9052_TBATRES_REG, ®_data); + if (ret) + return ret; + *data = (u16)reg_data; + + DA9052_DEBUG("In function: %s, TBATRES_REG value read (1)= 0x%X \n", + __func__, msg.data); + return SUCCESS; +} + +s32 da9052_adc_read_vbat(struct da9052 *da9052, u16 *data) +{ + s32 ret; + + ret = da9052_manual_read(da9052, DA9052_ADC_VBAT); + DA9052_DEBUG("In function: %s, VBAT value read (1)= 0x%X \n", + __func__, temp); + if (ret == -EIO) { + *data = 0; + return ret; + } else { + *data = ret; + return 0; + } + return 0; +} + + +static u16 filter_sample(u16 *buffer) +{ + u8 count; + u16 tempvalue = 0; + u16 ret; + + if (buffer == NULL) + return -EINVAL; + + for (count = 0; count < DA9052_FILTER_SIZE; count++) + tempvalue = tempvalue + *(buffer + count); + + ret = tempvalue/DA9052_FILTER_SIZE; + return ret; +} + +static s32 da9052_bat_get_battery_temperature(struct da9052_charger_device + *chg_device, u16 *buffer) +{ + + u8 count; + u16 filterqueue[DA9052_FILTER_SIZE]; + + /* Measure the battery temperature using ADC function. + Number of read equal to average filter size*/ + + for (count = 0; count < DA9052_FILTER_SIZE; count++) + if (da9052_adc_read_tbat(chg_device->da9052, &filterqueue[count])) + return -EIO; + + /* Apply Average filter */ + filterqueue[0] = filter_sample(filterqueue); + + chg_device->bat_temp = filterqueue[0]; + *buffer = chg_device->bat_temp; + + return SUCCESS; +} + +static s32 da9052_bat_get_chg_current(struct da9052_charger_device + *chg_device, u16 *buffer) +{ + if (chg_device->status == POWER_SUPPLY_STATUS_DISCHARGING) + return -EIO; + + /* Measure the Charger current using ADC function */ + if (da9052_adc_read_ich(chg_device->da9052, buffer)) + return -EIO; + + /* Convert the raw value in terms of mA */ + chg_device->chg_current = ichg_reg_to_mA(*buffer); + *buffer = chg_device->chg_current; + + return 0; +} + + +s32 da9052_bat_get_battery_voltage(struct da9052_charger_device *chg_device, + u16 *buffer) +{ + u8 count; + u16 filterqueue[DA9052_FILTER_SIZE]; + + /* Measure the battery voltage using ADC function. + Number of read equal to average filter size*/ + for (count = 0; count < DA9052_FILTER_SIZE; count++) + if (da9052_adc_read_vbat(chg_device->da9052, &filterqueue[count])) + return -EIO; + + /* Apply average filter */ + filterqueue[0] = filter_sample(filterqueue); + + /* Convert battery voltage raw value in terms of mV */ + chg_device->bat_voltage = volt_reg_to_mV(filterqueue[0]); + *buffer = chg_device->bat_voltage; + return 0; +} + +static void da9052_bat_status_update(struct da9052_charger_device + *chg_device) +{ + struct da9052_ssc_msg msg; + u16 current_value = 0; + u16 buffer =0; + u8 regvalue = 0; + u8 old_status = chg_device->status; + + DA9052_DEBUG("FUNCTION = %s \n", __func__); + + /* Read Status A register */ + msg.addr = DA9052_STATUSA_REG; + msg.data = 0; + da9052_lock(chg_device->da9052); + + if (chg_device->da9052->read(chg_device->da9052, &msg)) { + DA9052_DEBUG("%s : failed\n", __func__); + da9052_unlock(chg_device->da9052); + return; + } + regvalue = msg.data; + + /* Read Status B register */ + msg.addr = DA9052_STATUSB_REG; + msg.data = 0; + if (chg_device->da9052->read(chg_device->da9052, &msg)) { + DA9052_DEBUG("%s : failed\n", __func__); + da9052_unlock(chg_device->da9052); + return; + } + da9052_unlock(chg_device->da9052); + + /* If DCINDET and DCINSEL are set then connected charger is + WALL Charger unit */ + if( (regvalue & DA9052_STATUSA_DCINSEL) + && (regvalue & DA9052_STATUSA_DCINDET) ) { + + chg_device->charger_type = DA9052_WALL_CHARGER; + } + /* If VBUS_DET and VBUSEL are set then connected charger is + USB Type */ + else if((regvalue & DA9052_STATUSA_VBUSSEL) + && (regvalue & DA9052_STATUSA_VBUSDET)) { + if (regvalue & DA9052_STATUSA_VDATDET) { + chg_device->charger_type = DA9052_USB_CHARGER; + } + else { + /* Else it has to be USB Host charger */ + chg_device->charger_type = DA9052_USB_HUB; + } + } + /* Battery is discharging since charging device is not present */ + else + { + chg_device->charger_type = DA9052_NOCHARGER; + /* Eqv to DISCHARGING_WITHOUT_CHARGER state */ + chg_device->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + + if( chg_device->charger_type != DA9052_NOCHARGER ) { + /* if Charging end flag is set and Charging current is greater + than charging end limit then battery is charging */ + if ((msg.data & DA9052_STATUSB_CHGEND) != 0) { + + if(da9052_bat_get_chg_current(chg_device,¤t_value)) { + return; + } + + if( current_value >= chg_device->chg_end_current ) { + chg_device->status = POWER_SUPPLY_STATUS_CHARGING; + } + else { + /* Eqv to DISCHARGING_WITH_CHARGER state*/ + chg_device->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + } + /* if Charging end flag is cleared then battery is charging */ + else { + chg_device->status = POWER_SUPPLY_STATUS_CHARGING; + } + + if( POWER_SUPPLY_STATUS_CHARGING == chg_device->status){ + if(msg.data != DA9052_STATUSB_CHGPRE) { + /* Measure battery voltage. if battery voltage is greater than + (VCHG_BAT - VCHG_DROP) then battery is in the termintation mode. + */ + if(da9052_bat_get_battery_voltage(chg_device,&buffer)) { + DA9052_DEBUG("%s : failed\n",__FUNCTION__); + return ; + } + if(buffer > (chg_device->bat_target_voltage - + chg_device->charger_voltage_drop) && + ( chg_device->cal_capacity >= 99 ) ){ + chg_device->status = POWER_SUPPLY_STATUS_FULL; + } + + } + } + } + + if(chg_device->illegal) + chg_device->health = POWER_SUPPLY_HEALTH_UNKNOWN; + else if (chg_device->cal_capacity < chg_device->bat_capacity_limit_low) + chg_device->health = POWER_SUPPLY_HEALTH_DEAD; + else + chg_device->health = POWER_SUPPLY_HEALTH_GOOD; + + if ( chg_device->status != old_status) + power_supply_changed(&chg_device->psy); + + return; +} + +static s32 da9052_bat_suspend_charging(struct da9052_charger_device *chg_device) +{ + struct da9052_ssc_msg msg; + + if ((chg_device->status == POWER_SUPPLY_STATUS_DISCHARGING) || + (chg_device->status == POWER_SUPPLY_STATUS_NOT_CHARGING)) + return 0; + + msg.addr = DA9052_INPUTCONT_REG; + msg.data = 0; + da9052_lock(chg_device->da9052); + /* Read Input condition register */ + if (chg_device->da9052->read(chg_device->da9052, &msg)) { + da9052_unlock(chg_device->da9052); + return DA9052_SSC_FAIL; + } + + /* set both Wall charger and USB charger suspend bit */ + msg.data = set_bits(msg.data, DA9052_INPUTCONT_DCINSUSP); + msg.data = set_bits(msg.data, DA9052_INPUTCONT_VBUSSUSP); + + /* Write to Input control register */ + if (chg_device->da9052->write(chg_device->da9052, &msg)) { + da9052_unlock(chg_device->da9052); + DA9052_DEBUG("%s : failed\n", __func__); + return DA9052_SSC_FAIL; + } + da9052_unlock(chg_device->da9052); + + DA9052_DEBUG("%s : Sucess\n", __func__); + return 0; +} + +u32 interpolated(u32 vbat_lower, u32 vbat_upper, u32 level_lower, + u32 level_upper, u32 bat_voltage) +{ + s32 temp; + /*apply formula y= yk + (x - xk) * (yk+1 -yk)/(xk+1 -xk) */ + temp = ((level_upper - level_lower) * 1000)/(vbat_upper - vbat_lower); + temp = level_lower + (((bat_voltage - vbat_lower) * temp)/1000); + + return temp; +} + +s32 capture_first_correct_vbat_sample(struct da9052_charger_device *chg_device, +u16 *battery_voltage) +{ + static u8 count; + s32 ret = 0; + u32 temp_data = 0; + + ret = da9052_bat_get_battery_voltage(chg_device, + &bat_volt_arr[count]); + if (ret) + return ret; + count++; + + if (count < chg_device->vbat_first_valid_detect_iteration) + return FAILURE; + for (count = 0; count < + (chg_device->vbat_first_valid_detect_iteration - 1); + count++) { + temp_data = (bat_volt_arr[count] * + (chg_device->hysteresis_window_size))/100; + bat_hysteresis.upper_limit = bat_volt_arr[count] + temp_data; + bat_hysteresis.lower_limit = bat_volt_arr[count] - temp_data; + + if ((bat_volt_arr[count + 1] < bat_hysteresis.upper_limit) && + (bat_volt_arr[count + 1] > + bat_hysteresis.lower_limit)) { + *battery_voltage = (bat_volt_arr[count] + + bat_volt_arr[count+1]) / 2; + hys_flag = TRUE; + return 0; + } + } + + for (count = 0; count < + (chg_device->vbat_first_valid_detect_iteration - 1); + count++) + bat_volt_arr[count] = bat_volt_arr[count + 1]; + + return FAILURE; +} + + +s32 check_hystersis(struct da9052_charger_device *chg_device, u16 *bat_voltage) +{ + u8 ret = 0; + u32 offset = 0; + + /* Measure battery voltage using BAT internal function*/ + if (hys_flag == FALSE) { + ret = capture_first_correct_vbat_sample + (chg_device, &array_hys_batvoltage[0]); + if (ret) + return ret; + } + + ret = da9052_bat_get_battery_voltage + (chg_device, &array_hys_batvoltage[1]); + if (ret) + return ret; + *bat_voltage = array_hys_batvoltage[1]; + +#if DA9052_BAT_FILTER_HYS + printk(KERN_CRIT "\nBAT_LOG: Previous Battery Voltage = %d mV\n", + array_hys_batvoltage[0]); + printk(KERN_CRIT "\nBAT_LOG:Battery Voltage Before Filter = %d mV\n", + array_hys_batvoltage[1]); +#endif + /* Check if measured battery voltage value is within the hysteresis + window limit using measured battey votlage value */ + if ((bat_hysteresis.upper_limit < *bat_voltage) || + (bat_hysteresis.lower_limit > *bat_voltage)) { + + bat_hysteresis.index++; + if (bat_hysteresis.index == + chg_device->hysteresis_no_of_reading) { + /* Hysteresis Window is set to +- of + HYSTERESIS_WINDOW_SIZE percentage of current VBAT */ + bat_hysteresis.index = 0; + offset = ((*bat_voltage) * + chg_device->hysteresis_window_size)/ + 100; + bat_hysteresis.upper_limit = (*bat_voltage) + offset; + bat_hysteresis.lower_limit = (*bat_voltage) - offset; + } else { +#if DA9052_BAT_FILTER_HYS + printk(KERN_CRIT "CheckHystersis: Failed\n"); +#endif + return -EIO; + } + } else { + bat_hysteresis.index = 0; + offset = ((*bat_voltage) * + chg_device->hysteresis_window_size)/100; + bat_hysteresis.upper_limit = (*bat_voltage) + offset; + bat_hysteresis.lower_limit = (*bat_voltage) - offset; + } + + /* Digital C Filter, formula Yn = k Yn-1 + (1-k) Xn */ + *bat_voltage = ((chg_device->chg_hysteresis_const * + array_hys_batvoltage[0])/100) + + (((100 - chg_device->chg_hysteresis_const) * + array_hys_batvoltage[1])/100); + + if ((chg_device->status == POWER_SUPPLY_STATUS_DISCHARGING) && + (*bat_voltage > array_hys_batvoltage[0])) { + *bat_voltage = array_hys_batvoltage[0]; + } + + array_hys_batvoltage[0] = *bat_voltage; + +#if DA9052_BAT_FILTER_HYS + printk(KERN_CRIT "\nBAT_LOG:Battery Voltage After Filter = %d mV\n",\ + *bat_voltage); + +#endif + return 0; +} + +u8 select_temperature(u8 temp_index, u16 bat_temperature) +{ + u16 temp_temperature = 0; + temp_temperature = (temperature_lookup_ref[temp_index] + + temperature_lookup_ref[temp_index+1]) / 2; + + if (bat_temperature >= temp_temperature) { + temp_index += 1; + return temp_index; + } else + return temp_index; +} + +s32 da9052_bat_level_update(struct da9052_charger_device *chg_device) +{ + u16 bat_temperature; + u16 bat_voltage; + u32 vbat_lower, vbat_upper, level_upper, level_lower, level; + u8 access_index = 0; + u8 index = 0, ret; + u8 flag = FALSE; + + ret = 0; + vbat_lower = 0; + vbat_upper = 0; + level_upper = 0; + level_lower = 0; + + ret = check_hystersis(chg_device, &bat_voltage); + if (ret) + return ret; + + ret = da9052_bat_get_battery_temperature(chg_device, + &bat_temperature); + if (ret) + return ret; + + for (index = 0; index < (DA9052_NO_OF_LOOKUP_TABLE-1); index++) { + if (bat_temperature <= temperature_lookup_ref[0]) { + access_index = 0; + break; + } else if (bat_temperature > + temperature_lookup_ref[DA9052_NO_OF_LOOKUP_TABLE]){ + access_index = DA9052_NO_OF_LOOKUP_TABLE - 1; + break; + } else if ((bat_temperature >= temperature_lookup_ref[index]) && + (bat_temperature >= temperature_lookup_ref[index+1])) { + access_index = select_temperature(index, + bat_temperature); + break; + } + } + if (bat_voltage >= vbat_vs_capacity_look_up[access_index][0][0]) { + chg_device->cal_capacity = 100; + return 0; + } + if (bat_voltage <= vbat_vs_capacity_look_up[access_index] + [DA9052_LOOK_UP_TABLE_SIZE-1][0]){ + chg_device->cal_capacity = 0; + return 0; + } + flag = FALSE; + + for (index = 0; index < (DA9052_LOOK_UP_TABLE_SIZE-1); index++) { + if ((bat_voltage <= + vbat_vs_capacity_look_up[access_index][index][0]) && + (bat_voltage >= + vbat_vs_capacity_look_up[access_index][index+1][0])) { + vbat_upper = + vbat_vs_capacity_look_up[access_index][index][0]; + vbat_lower = + vbat_vs_capacity_look_up[access_index][index+1][0]; + level_upper = + vbat_vs_capacity_look_up[access_index][index][1]; + level_lower = + vbat_vs_capacity_look_up[access_index][index+1][1]; + flag = TRUE; + break; + } + } + if (!flag) + return -EIO; + + level = interpolated(vbat_lower, vbat_upper, level_lower, + level_upper, bat_voltage); + chg_device->cal_capacity = level; + DA9052_DEBUG(" TOTAl_BAT_CAPACITY : %d\n", chg_device->cal_capacity); + return 0; +} + +void da9052_bat_tbat_handler(struct da9052_eh_nb *eh_data, unsigned int event) +{ + struct da9052_charger_device *chg_device = + container_of(eh_data, struct da9052_charger_device, tbat_eh_data); + + chg_device->health = POWER_SUPPLY_HEALTH_OVERHEAT; + +} + +static s32 da9052_bat_register_event(struct da9052_charger_device *chg_device) +{ + s32 ret; + + if (event_status.da9052_event_tbat == FALSE) { + chg_device->tbat_eh_data.eve_type = TBAT_EVE; + chg_device->tbat_eh_data.call_back =da9052_bat_tbat_handler; + DA9052_DEBUG("events = %d\n",TBAT_EVE); + ret = chg_device->da9052->register_event_notifier + (chg_device->da9052, &chg_device->tbat_eh_data); + if (ret) + return -EIO; + event_status.da9052_event_tbat = TRUE; + } + + return 0; +} + +static s32 da9052_bat_unregister_event(struct da9052_charger_device *chg_device) +{ + s32 ret; + + if (event_status.da9052_event_tbat) { + ret = + chg_device->da9052->unregister_event_notifier + (chg_device->da9052, &chg_device->tbat_eh_data); + if (ret) + return -EIO; + event_status.da9052_event_tbat = FALSE; + } + + return 0; +} + +static int da9052_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct da9052_charger_device *chg_device = + container_of(psy, struct da9052_charger_device, psy); + + /* Validate battery presence */ + if( chg_device->illegal && psp != POWER_SUPPLY_PROP_PRESENT ) { + return -ENODEV; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = chg_device->status; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = (chg_device->charger_type == DA9052_NOCHARGER) ? 0: 1; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = chg_device->illegal; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = chg_device->health; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = chg_device->bat_target_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = chg_device->bat_volt_cutoff * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = chg_device->bat_voltage * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = chg_device->chg_current * 1000; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = chg_device->cal_capacity; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = bat_temp_reg_to_C(chg_device->bat_temp); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = chg_device->technology; + break; + default: + return -EINVAL; + break; + } + return 0; +} + + +static u8 detect_illegal_battery(struct da9052_charger_device *chg_device) +{ + u16 buffer = 0; + s32 ret = 0; + + /* Measure battery temeperature */ + ret = da9052_bat_get_battery_temperature(chg_device, &buffer); + if (ret) { + DA9052_DEBUG("%s: Battery temperature measurement failed \n", + __func__); + return ret; + } + + if (buffer > chg_device->bat_with_no_resistor) + chg_device->illegal = TRUE; + else + chg_device->illegal = FALSE; + + + /* suspend charging of battery if illegal battey is detected */ + if (chg_device->illegal) + da9052_bat_suspend_charging(chg_device); + + return chg_device->illegal; +} + +void da9052_update_bat_properties(struct da9052_charger_device *chg_device) +{ + /* Get Bat status and type */ + da9052_bat_status_update(chg_device); + da9052_bat_level_update(chg_device); +} + +static void da9052_bat_external_power_changed(struct power_supply *psy) +{ + struct da9052_charger_device *chg_device = + container_of(psy, struct da9052_charger_device, psy); + + cancel_delayed_work(&chg_device->monitor_work); + queue_delayed_work(chg_device->monitor_wqueue, &chg_device->monitor_work, HZ/10); +} + + +static void da9052_bat_work(struct work_struct *work) +{ + struct da9052_charger_device *chg_device = container_of(work, + struct da9052_charger_device,monitor_work.work); + + da9052_update_bat_properties(chg_device); + queue_delayed_work(chg_device->monitor_wqueue, &chg_device->monitor_work, HZ * 8); +} + +static enum power_supply_property da9052_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + +}; + +static s32 __devinit da9052_bat_probe(struct platform_device *pdev) +{ + struct da9052_charger_device *chg_device; + u8 reg_data; + int ret; + + chg_device = kzalloc(sizeof(*chg_device), GFP_KERNEL); + if (!chg_device) + return -ENOMEM; + + chg_device->da9052 = dev_get_drvdata(pdev->dev.parent); + platform_set_drvdata(pdev, chg_device); + + chg_device->psy.name = DA9052_BAT_DEVICE_NAME; + chg_device->psy.type = POWER_SUPPLY_TYPE_BATTERY; + chg_device->psy.properties = da9052_bat_props; + chg_device->psy.num_properties = ARRAY_SIZE(da9052_bat_props); + chg_device->psy.get_property = da9052_bat_get_property; + chg_device->psy.external_power_changed = da9052_bat_external_power_changed; + chg_device->psy.use_for_apm = 1; + chg_device->charger_type = DA9052_NOCHARGER; + chg_device->status = POWER_SUPPLY_STATUS_UNKNOWN; + chg_device->health = POWER_SUPPLY_HEALTH_UNKNOWN; + chg_device->technology = POWER_SUPPLY_TECHNOLOGY_LION; + chg_device->bat_with_no_resistor = 62; + chg_device->bat_capacity_limit_low = 4; + chg_device->bat_capacity_limit_high = 70; + chg_device->bat_capacity_full = 100; + chg_device->bat_volt_cutoff = 2800; + chg_device->vbat_first_valid_detect_iteration = 3; + chg_device->hysteresis_window_size =1; + chg_device->chg_hysteresis_const =89; + chg_device->hysteresis_reading_interval =1000; + chg_device->hysteresis_no_of_reading =10; + + ret = da9052_read(chg_device->da9052, DA9052_CHGCONT_REG, ®_data); + if (ret) + goto err_charger_init; + chg_device->charger_voltage_drop = bat_drop_reg_to_mV(reg_data && + DA9052_CHGCONT_TCTR); + chg_device->bat_target_voltage = + bat_reg_to_mV(reg_data && DA9052_CHGCONT_VCHGBAT); + + ret = da9052_read(chg_device->da9052, DA9052_ICHGEND_REG, ®_data); + if (ret) + goto err_charger_init; + chg_device->chg_end_current = ichg_reg_to_mA(reg_data); + + bat_hysteresis.upper_limit = 0; + bat_hysteresis.lower_limit = 0; + bat_hysteresis.hys_flag = 0; + + chg_device->illegal = FALSE; + detect_illegal_battery(chg_device); + + da9052_bat_register_event(chg_device); + if (ret) + goto err_charger_init; + + ret = power_supply_register(&pdev->dev, &chg_device->psy); + if (ret) + goto err_charger_init; + + INIT_DELAYED_WORK(&chg_device->monitor_work, da9052_bat_work); + chg_device->monitor_wqueue = create_singlethread_workqueue(pdev->dev.bus_id); + if (!chg_device->monitor_wqueue) { + goto err_charger_init; + } + queue_delayed_work(chg_device->monitor_wqueue, &chg_device->monitor_work, HZ * 1); + + return 0; + +err_charger_init: + platform_set_drvdata(pdev, NULL); + kfree(chg_device); + return ret; +} +static int __devexit da9052_bat_remove(struct platform_device *dev) +{ + struct da9052_charger_device *chg_device = platform_get_drvdata(dev); + + /* unregister the events.*/ + da9052_bat_unregister_event(chg_device); + + cancel_rearming_delayed_workqueue(chg_device->monitor_wqueue, + &chg_device->monitor_work); + destroy_workqueue(chg_device->monitor_wqueue); + + power_supply_unregister(&chg_device->psy); + + return 0; +} + +static struct platform_driver da9052_bat_driver = { + .probe = da9052_bat_probe, + .remove = __devexit_p(da9052_bat_remove), + .driver.name = DA9052_BAT_DEVICE_NAME, + .driver.owner = THIS_MODULE, +}; + +static int __init da9052_bat_init(void) +{ + printk(banner); + return platform_driver_register(&da9052_bat_driver); +} + +static void __exit da9052_bat_exit(void) +{ + // To remove printk("DA9052: Unregistering BAT device.\n"); + platform_driver_unregister(&da9052_bat_driver); +} + +module_init(da9052_bat_init); +module_exit(da9052_bat_exit); + +MODULE_AUTHOR("Dialog Semiconductor Ltd"); +MODULE_DESCRIPTION("DA9052 BAT Device Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/da9052-regulator.c b/drivers/regulator/da9052-regulator.c new file mode 100644 index 000000000000..780c82427b95 --- /dev/null +++ b/drivers/regulator/da9052-regulator.c @@ -0,0 +1,487 @@ +/* + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * 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. + * + * da9052-regulator.c: Regulator driver for DA9052 + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/pm.h> + +static struct regulator_ops da9052_ldo_buck_ops; + + +struct regulator { + struct device *dev; + struct list_head list; + int uA_load; + int min_uV; + int max_uV; + int enabled; /* client has called enabled */ + char *supply_name; + struct device_attribute dev_attr; + struct regulator_dev *rdev; +}; + + + + +#define DA9052_LDO(_id, max, min, step_v, reg, mbits, cbits) \ +{\ + .reg_desc = {\ + .name = #_id,\ + .ops = &da9052_ldo_buck_ops,\ + .type = REGULATOR_VOLTAGE,\ + .id = _id,\ + .owner = THIS_MODULE,\ + },\ + .reg_const = {\ + .max_uV = (max) * 1000,\ + .min_uV = (min) * 1000,\ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE\ + | REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE,\ + .valid_modes_mask = REGULATOR_MODE_NORMAL,\ + },\ + .step_uV = (step_v) * 1000,\ + .reg_add = (reg),\ + .mask_bits = (mbits),\ + .en_bit_mask = (cbits),\ +} + +struct regulator_info { + struct regulator_desc reg_desc; + struct regulation_constraints reg_const; + int step_uV; + unsigned char reg_add; + unsigned char mask_bits; + unsigned char en_bit_mask; +}; + +struct da9052_regulator_priv { + struct da9052 *da9052; + struct regulator_dev *regulators[]; +}; + +struct regulator_info da9052_regulators[] = { + /* LD01 - LDO10*/ + DA9052_LDO(DA9052_LDO1, DA9052_LDO1_VOLT_UPPER, DA9052_LDO1_VOLT_LOWER, + DA9052_LDO1_VOLT_STEP, DA9052_LDO1_REG, + DA9052_LDO1_VLDO1, DA9052_LDO1_LDO1EN), + + DA9052_LDO(DA9052_LDO2, + DA9052_LDO2_VOLT_UPPER, DA9052_LDO2_VOLT_LOWER, + DA9052_LDO2_VOLT_STEP, DA9052_LDO2_REG, + DA9052_LDO2_VLDO2, + DA9052_LDO2_LDO2EN), + + DA9052_LDO(DA9052_LDO3, DA9052_LDO34_VOLT_UPPER, + DA9052_LDO34_VOLT_LOWER, + DA9052_LDO34_VOLT_STEP, DA9052_LDO3_REG, + DA9052_LDO3_VLDO3, DA9052_LDO3_LDO3EN), + + DA9052_LDO(DA9052_LDO4, DA9052_LDO34_VOLT_UPPER, + DA9052_LDO34_VOLT_LOWER, + DA9052_LDO34_VOLT_STEP, DA9052_LDO4_REG, + DA9052_LDO4_VLDO4, DA9052_LDO4_LDO4EN), + + DA9052_LDO(DA9052_LDO5, DA9052_LDO567810_VOLT_UPPER, + DA9052_LDO567810_VOLT_LOWER, + DA9052_LDO567810_VOLT_STEP, DA9052_LDO5_REG, + DA9052_LDO5_VLDO5, DA9052_LDO5_LDO5EN), + + DA9052_LDO(DA9052_LDO6, DA9052_LDO567810_VOLT_UPPER, + DA9052_LDO567810_VOLT_LOWER, + DA9052_LDO567810_VOLT_STEP, DA9052_LDO6_REG, + DA9052_LDO6_VLDO6, DA9052_LDO6_LDO6EN), + + DA9052_LDO(DA9052_LDO7, DA9052_LDO567810_VOLT_UPPER, + DA9052_LDO567810_VOLT_LOWER, + DA9052_LDO567810_VOLT_STEP, DA9052_LDO7_REG, + DA9052_LDO7_VLDO7, DA9052_LDO7_LDO7EN), + + DA9052_LDO(DA9052_LDO8, DA9052_LDO567810_VOLT_UPPER, + DA9052_LDO567810_VOLT_LOWER, + DA9052_LDO567810_VOLT_STEP, DA9052_LDO8_REG, + DA9052_LDO8_VLDO8, DA9052_LDO8_LDO8EN), + + DA9052_LDO(DA9052_LDO9, DA9052_LDO9_VOLT_UPPER, + DA9052_LDO9_VOLT_LOWER, + DA9052_LDO9_VOLT_STEP, + DA9052_LDO9_REG, DA9052_LDO9_VLDO9, + DA9052_LDO9_LDO9EN), + + DA9052_LDO(DA9052_LDO10, DA9052_LDO567810_VOLT_UPPER, + DA9052_LDO567810_VOLT_LOWER, + DA9052_LDO567810_VOLT_STEP, DA9052_LDO10_REG, + DA9052_LDO10_VLDO10, DA9052_LDO10_LDO10EN), + + /* BUCKS */ + DA9052_LDO(DA9052_BUCK_CORE, DA9052_BUCK_CORE_PRO_VOLT_UPPER, + DA9052_BUCK_CORE_PRO_VOLT_LOWER, + DA9052_BUCK_CORE_PRO_STEP, DA9052_BUCKCORE_REG, + DA9052_BUCKCORE_VBCORE, DA9052_BUCKCORE_BCOREEN), + + DA9052_LDO(DA9052_BUCK_PRO, DA9052_BUCK_CORE_PRO_VOLT_UPPER, + DA9052_BUCK_CORE_PRO_VOLT_LOWER, + DA9052_BUCK_CORE_PRO_STEP, DA9052_BUCKPRO_REG, + DA9052_BUCKPRO_VBPRO, DA9052_BUCKPRO_BPROEN), + + DA9052_LDO(DA9052_BUCK_MEM, DA9052_BUCK_MEM_VOLT_UPPER, + DA9052_BUCK_MEM_VOLT_LOWER, + DA9052_BUCK_MEM_STEP, DA9052_BUCKMEM_REG, + DA9052_BUCKMEM_VBMEM, DA9052_BUCKMEM_BMEMEN), +#if defined (CONFIG_PMIC_DA9052) + DA9052_LDO(DA9052_BUCK_PERI, DA9052_BUCK_PERI_VOLT_UPPER, + DA9052_BUCK_PERI_VOLT_LOWER, + DA9052_BUCK_PERI_STEP_BELOW_3000, DA9052_BUCKPERI_REG, + DA9052_BUCKPERI_VBPERI, DA9052_BUCKPERI_BPERIEN), +#elif defined (CONFIG_PMIC_DA9053AA) || (CONFIG_PMIC_DA9053Bx) + DA9052_LDO(DA9052_BUCK_PERI, DA9052_BUCK_PERI_VOLT_UPPER, + DA9052_BUCK_PERI_VOLT_LOWER, + DA9052_BUCK_PERI_STEP, DA9052_BUCKPERI_REG, + DA9052_BUCKPERI_VBPERI, DA9052_BUCKPERI_BPERIEN), +#endif +}; + +int da9052_ldo_buck_enable(struct regulator_dev *rdev) +{ + struct da9052_regulator_priv *priv = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + int ret = 0; + struct da9052_ssc_msg ssc_msg; + + ssc_msg.addr = da9052_regulators[id].reg_add; + ssc_msg.data = 0; + + da9052_lock(priv->da9052); + ret = priv->da9052->read(priv->da9052, &ssc_msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EIO; + } + + ssc_msg.data = (ssc_msg.data | da9052_regulators[id].en_bit_mask); + + ret = priv->da9052->write(priv->da9052, &ssc_msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EIO; + } + da9052_unlock(priv->da9052); + + return 0; +} +EXPORT_SYMBOL_GPL(da9052_ldo_buck_enable); +/* Code added to support additional attribure in sysfs - changestate */ + + + +int da9052_ldo_buck_disable(struct regulator_dev *rdev) +{ + struct da9052_regulator_priv *priv = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + int ret; + struct da9052_ssc_msg ssc_msg; + + ssc_msg.addr = da9052_regulators[id].reg_add; + ssc_msg.data = 0; + + da9052_lock(priv->da9052); + ret = priv->da9052->read(priv->da9052, &ssc_msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EIO; + } + + ssc_msg.data = (ssc_msg.data & ~(da9052_regulators[id].en_bit_mask)); + + ret = priv->da9052->write(priv->da9052, &ssc_msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EIO; + } + da9052_unlock(priv->da9052); + + return 0; +} +EXPORT_SYMBOL_GPL(da9052_ldo_buck_disable); +/* Code added to support additional attribure in sysfs - changestate */ + +static int da9052_ldo_buck_is_enabled(struct regulator_dev *rdev) +{ + struct da9052_regulator_priv *priv = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + int ret; + struct da9052_ssc_msg ssc_msg; + ssc_msg.addr = da9052_regulators[id].reg_add; + ssc_msg.data = 0; + + da9052_lock(priv->da9052); + ret = priv->da9052->read(priv->da9052, &ssc_msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EIO; + } + da9052_unlock(priv->da9052); + return (ssc_msg.data & da9052_regulators[id].en_bit_mask) != 0; +} + +int da9052_ldo_buck_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct da9052_regulator_priv *priv = rdev_get_drvdata(rdev); + struct da9052_ssc_msg ssc_msg; + int id = rdev_get_id(rdev); + int ret; + int ldo_volt = 0; + + /* Below if condition is there for added setvoltage attribute + in sysfs */ + if (0 == max_uV) + max_uV = da9052_regulators[id].reg_const.max_uV; + + /* Compare voltage range */ + if (min_uV > max_uV) + return -EINVAL; + + /* Check Minimum/ Maximum voltage range */ + if (min_uV < da9052_regulators[id].reg_const.min_uV || + min_uV > da9052_regulators[id].reg_const.max_uV) + return -EINVAL; + if (max_uV < da9052_regulators[id].reg_const.min_uV || + max_uV > da9052_regulators[id].reg_const.max_uV) + return -EINVAL; +#if defined (CONFIG_PMIC_DA9052) + /* Get the ldo register value */ + /* Varying step size for BUCK PERI */ + if ((da9052_regulators[id].reg_desc.id == DA9052_BUCK_PERI) && + (min_uV >= DA9052_BUCK_PERI_VALUES_3000)) { + ldo_volt = (DA9052_BUCK_PERI_VALUES_3000 - + da9052_regulators[id].reg_const.min_uV)/ + (da9052_regulators[id].step_uV); + ldo_volt += (min_uV - DA9052_BUCK_PERI_VALUES_3000)/ + (DA9052_BUCK_PERI_STEP_ABOVE_3000); + } else{ + ldo_volt = (min_uV - da9052_regulators[id].reg_const.min_uV)/ + (da9052_regulators[id].step_uV); + /* Check for maximum value */ + if ((ldo_volt * da9052_regulators[id].step_uV) + + da9052_regulators[id].reg_const.min_uV > max_uV) + return -EINVAL; + } +#elif defined (CONFIG_PMIC_DA9053AA) ||(CONFIG_PMIC_DA9053Bx) + ldo_volt = (min_uV - da9052_regulators[id].reg_const.min_uV)/ + (da9052_regulators[id].step_uV); + /* Check for maximum value */ + if ((ldo_volt * da9052_regulators[id].step_uV) + + da9052_regulators[id].reg_const.min_uV > max_uV) + return -EINVAL; +#endif + /* Configure LDO Voltage, CONF bits */ + ssc_msg.addr = da9052_regulators[id].reg_add; + ssc_msg.data = 0; + + /* Read register */ + da9052_lock(priv->da9052); + ret = priv->da9052->read(priv->da9052, &ssc_msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EIO; + } + + ssc_msg.data = (ssc_msg.data & ~(da9052_regulators[id].mask_bits)); + ssc_msg.data |= ldo_volt; + + ret = priv->da9052->write(priv->da9052, &ssc_msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EIO; + } + + /* Set the GO LDO/BUCk bits so that the voltage changes */ + ssc_msg.addr = DA9052_SUPPLY_REG; + ssc_msg.data = 0; + + ret = priv->da9052->read(priv->da9052, &ssc_msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EIO; + } + + switch (id) { + case DA9052_LDO2: + ssc_msg.data = (ssc_msg.data | DA9052_SUPPLY_VLDO2GO); + break; + case DA9052_LDO3: + ssc_msg.data = (ssc_msg.data | DA9052_SUPPLY_VLDO3GO); + break; + case DA9052_BUCK_CORE: + ssc_msg.data = (ssc_msg.data | DA9052_SUPPLY_VBCOREGO); + break; + case DA9052_BUCK_PRO: + ssc_msg.data = (ssc_msg.data | DA9052_SUPPLY_VBPROGO); + break; + case DA9052_BUCK_MEM: + ssc_msg.data = (ssc_msg.data | DA9052_SUPPLY_VBMEMGO); + break; + default: + da9052_unlock(priv->da9052); + return -EINVAL; + } + + ret = priv->da9052->write(priv->da9052, &ssc_msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EIO; + } + + da9052_unlock(priv->da9052); + + return 0; +} +EXPORT_SYMBOL_GPL(da9052_ldo_buck_set_voltage); +/* Code added to support additional attributes in sysfs - setvoltage */ + + +int da9052_ldo_buck_get_voltage(struct regulator_dev *rdev) +{ + struct da9052_regulator_priv *priv = rdev_get_drvdata(rdev); + struct da9052_ssc_msg ssc_msg; + int id = rdev_get_id(rdev); + int ldo_volt = 0; + int ldo_volt_uV = 0; + int ret; + + ssc_msg.addr = da9052_regulators[id].reg_add; + ssc_msg.data = 0; + /* Read register */ + da9052_lock(priv->da9052); + ret = priv->da9052->read(priv->da9052, &ssc_msg); + if (ret) { + da9052_unlock(priv->da9052); + return -EIO; + } + da9052_unlock(priv->da9052); + + ldo_volt = ssc_msg.data & da9052_regulators[id].mask_bits; +#if defined (CONFIG_PMIC_DA9052) + if (da9052_regulators[id].reg_desc.id == DA9052_BUCK_PERI) { + if (ldo_volt >= DA9052_BUCK_PERI_VALUES_UPTO_3000) { + ldo_volt_uV = ((DA9052_BUCK_PERI_VALUES_UPTO_3000 * + da9052_regulators[id].step_uV) + + da9052_regulators[id].reg_const.min_uV); + ldo_volt_uV = (ldo_volt_uV + + (ldo_volt - DA9052_BUCK_PERI_VALUES_UPTO_3000) + * (DA9052_BUCK_PERI_STEP_ABOVE_3000)); + } else { + ldo_volt_uV = + (ldo_volt * da9052_regulators[id].step_uV) + + da9052_regulators[id].reg_const.min_uV; + } + } else { + ldo_volt_uV = (ldo_volt * da9052_regulators[id].step_uV) + + da9052_regulators[id].reg_const.min_uV; + } +#elif defined (CONFIG_PMIC_DA9053AA) || (CONFIG_PMIC_DA9053Bx) + ldo_volt_uV = (ldo_volt * da9052_regulators[id].step_uV) + + da9052_regulators[id].reg_const.min_uV; +#endif + return ldo_volt_uV; +} +EXPORT_SYMBOL_GPL(da9052_ldo_buck_get_voltage); +/* Code added to support additional attributes in sysfs - setvoltage */ + + +static struct regulator_ops da9052_ldo_buck_ops = { + .is_enabled = da9052_ldo_buck_is_enabled, + .enable = da9052_ldo_buck_enable, + .disable = da9052_ldo_buck_disable, + .get_voltage = da9052_ldo_buck_get_voltage, + .set_voltage = da9052_ldo_buck_set_voltage, +}; + +static int __devinit da9052_regulator_probe(struct platform_device *pdev) +{ + struct da9052_regulator_priv *priv; + struct da9052_regulator_platform_data *pdata = + (pdev->dev.platform_data); + struct da9052 *da9052 = dev_get_drvdata(pdev->dev.parent); + struct regulator_init_data *init_data; + int i, ret = 0; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + priv->da9052 = da9052; + for (i = 0; i < 14; i++) { + + init_data = &pdata->regulators[i]; + init_data->driver_data = da9052; + pdev->dev.platform_data = init_data; + priv->regulators[i] = regulator_register( + &da9052_regulators[i].reg_desc, + &pdev->dev, priv); + if (IS_ERR(priv->regulators[i])) { + ret = PTR_ERR(priv->regulators[i]); + goto err; + } + } + platform_set_drvdata(pdev, priv); + return 0; +err: + while (--i >= 0) + regulator_unregister(priv->regulators[i]); + kfree(priv); + return ret; +} + +static int __devexit da9052_regulator_remove(struct platform_device *pdev) +{ + struct da9052_regulator_priv *priv = platform_get_drvdata(pdev); + struct da9052_platform_data *pdata = pdev->dev.platform_data; + int i; + + for (i = 0; i < pdata->num_regulators; i++) + regulator_unregister(priv->regulators[i]); + + return 0; +} + +static struct platform_driver da9052_regulator_driver = { + .probe = da9052_regulator_probe, + .remove = __devexit_p(da9052_regulator_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init da9052_regulator_init(void) +{ + return platform_driver_register(&da9052_regulator_driver); +} +subsys_initcall(da9052_regulator_init); + +static void __exit da9052_regulator_exit(void) +{ + platform_driver_unregister(&da9052_regulator_driver); +} +module_exit(da9052_regulator_exit); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("Power Regulator driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/rtc/rtc-da9052.c b/drivers/rtc/rtc-da9052.c new file mode 100644 index 000000000000..be80c228d5a3 --- /dev/null +++ b/drivers/rtc/rtc-da9052.c @@ -0,0 +1,694 @@ +/* + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * 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. + * + * rtc-da9052.c: RTC driver for DA9052 + */ + +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/rtc.h> + +#define DRIVER_NAME "da9052-rtc" +#define ENABLE 1 +#define DISABLE 0 + +struct da9052_rtc { + struct rtc_device *rtc; + struct da9052 *da9052; + struct da9052_eh_nb eh_data; + unsigned char is_min_alarm; + unsigned char enable_tick_alarm; + unsigned char enable_clk_buffer; + unsigned char set_osc_trim_freq; +}; + +static int da9052_rtc_enable_alarm(struct da9052 *da9052, unsigned char flag); + +void da9052_rtc_notifier(struct da9052_eh_nb *eh_data, unsigned int event) +{ + struct da9052_rtc *rtc = + container_of(eh_data, struct da9052_rtc, eh_data); + struct da9052_ssc_msg msg; + unsigned int ret; + + /* Check the alarm type - TIMER or TICK */ + msg.addr = DA9052_ALARMMI_REG; + + da9052_lock(rtc->da9052); + ret = rtc->da9052->read(rtc->da9052, &msg); + if (ret != 0) { + da9052_unlock(rtc->da9052); + return; + } + + da9052_unlock(rtc->da9052); + + + if (msg.data & DA9052_ALARMMI_ALARMTYPE) { + da9052_rtc_enable_alarm(rtc->da9052, 0); + printk(KERN_INFO "RTC: TIMER ALARM\n"); + } else { + kobject_uevent(&rtc->rtc->dev.kobj, KOBJ_CHANGE); + printk(KERN_INFO "RTC: TICK ALARM\n"); + } +} + +static int da9052_rtc_validate_parameters(struct rtc_time *rtc_tm) +{ + + if (rtc_tm->tm_sec > DA9052_RTC_SECONDS_LIMIT) + return DA9052_RTC_INVALID_SECONDS; + + if (rtc_tm->tm_min > DA9052_RTC_MINUTES_LIMIT) + return DA9052_RTC_INVALID_MINUTES; + + if (rtc_tm->tm_hour > DA9052_RTC_HOURS_LIMIT) + return DA9052_RTC_INVALID_HOURS; + + if (rtc_tm->tm_mday == 0) + return DA9052_RTC_INVALID_DAYS; + + if ((rtc_tm->tm_mon > DA9052_RTC_MONTHS_LIMIT) || + (rtc_tm->tm_mon == 0)) + return DA9052_RTC_INVALID_MONTHS; + + if (rtc_tm->tm_year > DA9052_RTC_YEARS_LIMIT) + return DA9052_RTC_INVALID_YEARS; + + if ((rtc_tm->tm_mon == FEBRUARY)) { + if (((rtc_tm->tm_year % 4 == 0) && + (rtc_tm->tm_year % 100 != 0)) || + (rtc_tm->tm_year % 400 == 0)) { + if (rtc_tm->tm_mday > 29) + return DA9052_RTC_INVALID_DAYS; + } else if (rtc_tm->tm_mday > 28) { + return DA9052_RTC_INVALID_DAYS; + } + } + + if (((rtc_tm->tm_mon == APRIL) || (rtc_tm->tm_mon == JUNE) || + (rtc_tm->tm_mon == SEPTEMBER) || (rtc_tm->tm_mon == NOVEMBER)) + && (rtc_tm->tm_mday == 31)) { + return DA9052_RTC_INVALID_DAYS; + } + + + return 0; +} + +static int da9052_rtc_settime(struct da9052 *da9052, struct rtc_time *rtc_tm) +{ + + struct da9052_ssc_msg msg_arr[6]; + int validate_param = 0; + unsigned char loop_index = 0; + int ret = 0; + + + /* System compatability */ + rtc_tm->tm_year -= 100; + rtc_tm->tm_mon += 1; + + validate_param = da9052_rtc_validate_parameters(rtc_tm); + if (validate_param) + return validate_param; + + msg_arr[loop_index].addr = DA9052_COUNTS_REG; + msg_arr[loop_index++].data = DA9052_COUNTS_MONITOR | rtc_tm->tm_sec; + + msg_arr[loop_index].addr = DA9052_COUNTMI_REG; + msg_arr[loop_index].data = 0; + msg_arr[loop_index++].data = rtc_tm->tm_min; + + msg_arr[loop_index].addr = DA9052_COUNTH_REG; + msg_arr[loop_index].data = 0; + msg_arr[loop_index++].data = rtc_tm->tm_hour; + + msg_arr[loop_index].addr = DA9052_COUNTD_REG; + msg_arr[loop_index].data = 0; + msg_arr[loop_index++].data = rtc_tm->tm_mday; + + msg_arr[loop_index].addr = DA9052_COUNTMO_REG; + msg_arr[loop_index].data = 0; + msg_arr[loop_index++].data = rtc_tm->tm_mon; + + msg_arr[loop_index].addr = DA9052_COUNTY_REG; + msg_arr[loop_index].data = 0; + msg_arr[loop_index++].data = rtc_tm->tm_year; + + da9052_lock(da9052); + ret = da9052->write_many(da9052, msg_arr, loop_index); + if (ret != 0) { + da9052_unlock(da9052); + return ret; + } + + da9052_unlock(da9052); + return 0; +} + +static int da9052_rtc_gettime(struct da9052 *da9052, struct rtc_time *rtc_tm) +{ + + struct da9052_ssc_msg msg[6]; + unsigned char loop_index = 0; + int validate_param = 0; + int ret = 0; + + msg[loop_index].data = 0; + msg[loop_index++].addr = DA9052_COUNTS_REG; + + msg[loop_index].data = 0; + msg[loop_index++].addr = DA9052_COUNTMI_REG; + + msg[loop_index].data = 0; + msg[loop_index++].addr = DA9052_COUNTH_REG; + + msg[loop_index].data = 0; + msg[loop_index++].addr = DA9052_COUNTD_REG; + + msg[loop_index].data = 0; + msg[loop_index++].addr = DA9052_COUNTMO_REG; + + msg[loop_index].data = 0; + msg[loop_index++].addr = DA9052_COUNTY_REG; + + da9052_lock(da9052); + ret = da9052->read_many(da9052, msg, loop_index); + if (ret != 0) { + da9052_unlock(da9052); + return ret; + } + da9052_unlock(da9052); + + rtc_tm->tm_year = msg[--loop_index].data & DA9052_COUNTY_COUNTYEAR; + rtc_tm->tm_mon = msg[--loop_index].data & DA9052_COUNTMO_COUNTMONTH; + rtc_tm->tm_mday = msg[--loop_index].data & DA9052_COUNTD_COUNTDAY; + rtc_tm->tm_hour = msg[--loop_index].data & DA9052_COUNTH_COUNTHOUR; + rtc_tm->tm_min = msg[--loop_index].data & DA9052_COUNTMI_COUNTMIN; + rtc_tm->tm_sec = msg[--loop_index].data & DA9052_COUNTS_COUNTSEC; + + validate_param = da9052_rtc_validate_parameters(rtc_tm); + if (validate_param) + return validate_param; + + /* System compatability */ + rtc_tm->tm_year += 100; + rtc_tm->tm_mon -= 1; + return 0; +} + +static int da9052_alarm_gettime(struct da9052 *da9052, struct rtc_time *rtc_tm) +{ + struct da9052_ssc_msg msg[5]; + unsigned char loop_index = 0; + int validate_param = 0; + int ret = 0; + + msg[loop_index].data = 0; + msg[loop_index++].addr = DA9052_ALARMMI_REG; + + msg[loop_index].data = 0; + msg[loop_index++].addr = DA9052_ALARMH_REG; + + msg[loop_index].data = 0; + msg[loop_index++].addr = DA9052_ALARMD_REG; + + msg[loop_index].data = 0; + msg[loop_index++].addr = DA9052_ALARMMO_REG; + + msg[loop_index].data = 0; + msg[loop_index++].addr = DA9052_ALARMY_REG; + + da9052_lock(da9052); + ret = da9052->read_many(da9052, msg, loop_index); + if (ret != 0) { + da9052_unlock(da9052); + return ret; + } + da9052_unlock(da9052); + + rtc_tm->tm_year = msg[--loop_index].data & DA9052_ALARMY_ALARMYEAR; + rtc_tm->tm_mon = msg[--loop_index].data & DA9052_ALARMMO_ALARMMONTH; + rtc_tm->tm_mday = msg[--loop_index].data & DA9052_ALARMD_ALARMDAY; + rtc_tm->tm_hour = msg[--loop_index].data & DA9052_ALARMH_ALARMHOUR; + rtc_tm->tm_min = msg[--loop_index].data & DA9052_ALARMMI_ALARMMIN; + + validate_param = da9052_rtc_validate_parameters(rtc_tm); + if (validate_param) + return validate_param; + + /* System compatability */ + rtc_tm->tm_year += 100; + rtc_tm->tm_mon -= 1; + + return 0; +} + +static int da9052_alarm_settime(struct da9052 *da9052, struct rtc_time *rtc_tm) +{ + + struct da9052_ssc_msg msg_arr[5]; + struct da9052_ssc_msg msg; + int validate_param = 0; + unsigned char loop_index = 0; + int ret = 0; + + rtc_tm->tm_sec = 0; + + /* System compatability */ + rtc_tm->tm_year -= 100; + rtc_tm->tm_mon += 1; + + validate_param = da9052_rtc_validate_parameters(rtc_tm); + if (validate_param) + return validate_param; + + msg.addr = DA9052_ALARMMI_REG; + msg.data = 0; + + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret != 0) { + da9052_unlock(da9052); + return ret; + } + + msg.data = msg.data & ~(DA9052_ALARMMI_ALARMMIN); + msg.data |= rtc_tm->tm_min; + + msg_arr[loop_index].addr = DA9052_ALARMMI_REG; + msg_arr[loop_index].data = 0; + msg_arr[loop_index++].data = msg.data; + + msg_arr[loop_index].addr = DA9052_ALARMH_REG; + msg_arr[loop_index].data = 0; + msg_arr[loop_index++].data = rtc_tm->tm_hour; + + msg_arr[loop_index].addr = DA9052_ALARMD_REG; + msg_arr[loop_index].data = 0; + msg_arr[loop_index++].data = rtc_tm->tm_mday; + + msg_arr[loop_index].addr = DA9052_ALARMMO_REG; + msg_arr[loop_index].data = 0; + msg_arr[loop_index++].data = rtc_tm->tm_mon; + + msg.addr = DA9052_ALARMY_REG; + msg.data = 0; + ret = da9052->read(da9052, &msg); + if (ret != 0) { + da9052_unlock(da9052); + return ret; + } + + msg.data = msg.data & ~(DA9052_ALARMY_ALARMYEAR); + + + msg.data |= rtc_tm->tm_year; + msg_arr[loop_index].addr = DA9052_ALARMY_REG; + msg_arr[loop_index].data = 0; + msg_arr[loop_index++].data = msg.data; + + ret = da9052->write_many(da9052, msg_arr, loop_index); + if (ret) { + da9052_unlock(da9052); + return ret; + } + + da9052_unlock(da9052); + return 0; +} + +static int da9052_rtc_get_alarm_status(struct da9052 *da9052) +{ + struct da9052_ssc_msg msg; + int ret = 0; + + msg.addr = DA9052_ALARMY_REG; + msg.data = 0; + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret != 0) { + da9052_unlock(da9052); + return ret; + } + + da9052_unlock(da9052); + msg.data &= DA9052_ALARMY_ALARMON; + + return (msg.data > 0) ? 1 : 0; +} + + +static int da9052_rtc_enable_alarm(struct da9052 *da9052, unsigned char flag) +{ + struct da9052_ssc_msg msg; + int ret = 0; + + msg.addr = DA9052_ALARMY_REG; + da9052_lock(da9052); + ret = da9052->read(da9052, &msg); + if (ret != 0) { + da9052_unlock(da9052); + return ret; + } + + if (flag) + msg.data = msg.data | DA9052_ALARMY_ALARMON; + else + msg.data = msg.data & ~(DA9052_ALARMY_ALARMON); + + ret = da9052->write(da9052, &msg); + if (ret != 0) { + da9052_unlock(da9052); + return ret; + } + da9052_unlock(da9052); + + return 0; +} + + +static ssize_t da9052_rtc_mask_irq(struct da9052 *da9052) + { + unsigned char data = 0; + ssize_t ret = 0; + struct da9052_ssc_msg ssc_msg; + + ssc_msg.addr = DA9052_IRQMASKA_REG; + ssc_msg.data = 0; + + da9052_lock(da9052); + ret = da9052->read(da9052, &ssc_msg); + if (ret != 0) { + da9052_unlock(da9052); + return ret; + } + + data = ret; + ssc_msg.data = data |= DA9052_IRQMASKA_MALRAM; + + ret = da9052->write(da9052, &ssc_msg); + if (ret != 0) { + da9052_unlock(da9052); + return ret; + } + + da9052_unlock(da9052); + return 0; +} + + +static ssize_t da9052_rtc_unmask_irq(struct da9052 *da9052) +{ + unsigned char data = 0; + ssize_t ret = 0; + struct da9052_ssc_msg ssc_msg; + + ssc_msg.addr = DA9052_IRQMASKA_REG; + ssc_msg.data = 0; + + da9052_lock(da9052); + ret = da9052->read(da9052, &ssc_msg); + if (ret != 0) { + da9052_unlock(da9052); + return ret; + } + + data = ret; + ssc_msg.data = data &= ~DA9052_IRQMASKA_MALRAM; + + ret = da9052->write(da9052, &ssc_msg); + if (ret != 0) { + da9052_unlock(da9052); + return ret; + } + + da9052_unlock(da9052); + return 0; + +} + +static int da9052_rtc_class_ops_gettime + (struct device *dev, struct rtc_time *rtc_tm) +{ + int ret; + struct da9052 *da9052 = dev->parent->driver_data; + ret = da9052_rtc_gettime(da9052, rtc_tm); + if (ret) + return ret; + return 0; +} + + +static int da9052_rtc_class_ops_settime(struct device *dev, struct rtc_time *tm) +{ + int ret; + struct da9052 *da9052 = dev->parent->driver_data; + ret = da9052_rtc_settime(da9052, tm); + + return ret; +} + +static int da9052_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + int ret; + struct rtc_time *tm = &alrm->time; + struct da9052 *da9052 = dev->parent->driver_data; + ret = da9052_alarm_gettime(da9052, tm); + + if (ret) + return ret; + + alrm->enabled = da9052_rtc_get_alarm_status(da9052); + + return 0; + +} + +static int da9052_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + int ret = 0; + struct rtc_time *tm = &alrm->time; + struct da9052 *da9052 = dev->parent->driver_data; + + ret = da9052_alarm_settime(da9052, tm); + + if (ret) + return ret; + + ret = da9052_rtc_enable_alarm(da9052, 1); + + return ret; +} + +static int da9052_rtc_update_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct da9052_rtc *priv = dev_get_drvdata(dev); + int ret = -ENODATA; + + da9052_lock(priv->da9052); + + ret = (enabled ? da9052_rtc_unmask_irq : da9052_rtc_mask_irq) + (priv->da9052); + + da9052_unlock(priv->da9052); + + return ret; +} + +static int da9052_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct da9052_rtc *priv = dev_get_drvdata(dev); + + if (enabled) + return da9052_rtc_enable_alarm(priv->da9052, enabled); + else + return da9052_rtc_enable_alarm(priv->da9052, enabled); +} + +static const struct rtc_class_ops da9052_rtc_ops = { + .read_time = da9052_rtc_class_ops_gettime, + .set_time = da9052_rtc_class_ops_settime, + .read_alarm = da9052_rtc_readalarm, + .set_alarm = da9052_rtc_setalarm, +#if 0 + .update_irq_enable = da9052_rtc_update_irq_enable, + .alarm_irq_enable = da9052_rtc_alarm_irq_enable, +#endif +}; + + +static int __devinit da9052_rtc_probe(struct platform_device *pdev) +{ + int ret; + struct da9052_rtc *priv; + struct da9052_ssc_msg ssc_msg; + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->da9052 = dev_get_drvdata(pdev->dev.parent); + platform_set_drvdata(pdev, priv); + + /* Added to support sysfs wakealarm attribute */ + pdev->dev.power.can_wakeup = 1; + /* Added to support sysfs wakealarm attribute */ + + /* Set the EH structure */ + priv->eh_data.eve_type = ALARM_EVE; + priv->eh_data.call_back = &da9052_rtc_notifier; + ret = priv->da9052->register_event_notifier(priv->da9052, + &priv->eh_data); + if (ret) + goto err_register_alarm; + + priv->is_min_alarm = 1; + priv->enable_tick_alarm = 1; + priv->enable_clk_buffer = 1; + priv->set_osc_trim_freq = 5; + /* Enable/Disable TICK Alarm */ + /* Read ALARM YEAR register */ + ssc_msg.addr = DA9052_ALARMY_REG; + ssc_msg.data = 0; + + da9052_lock(priv->da9052); + ret = priv->da9052->read(priv->da9052, &ssc_msg); + if (ret != 0) { + da9052_unlock(priv->da9052); + goto err_ssc_comm; + } + + if (priv->enable_tick_alarm) + ssc_msg.data = (ssc_msg.data | DA9052_ALARMY_TICKON); + else + ssc_msg.data = + ((ssc_msg.data & ~(DA9052_ALARMY_TICKON))); + + ret = priv->da9052->write(priv->da9052, &ssc_msg); + if (ret != 0) { + da9052_unlock(priv->da9052); + goto err_ssc_comm; + } + + /* Set TICK Alarm to 1 minute or 1 sec */ + /* Read ALARM MINUTES register */ + ssc_msg.addr = DA9052_ALARMMI_REG; + ssc_msg.data = 0; + + ret = priv->da9052->read(priv->da9052, &ssc_msg); + if (ret != 0) { + da9052_unlock(priv->da9052); + goto err_ssc_comm; + } + + if (priv->is_min_alarm) + /* Set 1 minute tick type */ + ssc_msg.data = (ssc_msg.data | DA9052_ALARMMI_TICKTYPE); + else + /* Set 1 sec tick type */ + ssc_msg.data = (ssc_msg.data & ~(DA9052_ALARMMI_TICKTYPE)); + + ret = priv->da9052->write(priv->da9052, &ssc_msg); + if (ret != 0) { + da9052_unlock(priv->da9052); + goto err_ssc_comm; + } + + /* Enable/Disable Clock buffer in Power Down Mode */ + ssc_msg.addr = DA9052_PDDIS_REG; + ssc_msg.data = 0; + + ret = priv->da9052->read(priv->da9052, &ssc_msg); + if (ret != 0) { + da9052_unlock(priv->da9052); + goto err_ssc_comm; + } + + if (priv->enable_clk_buffer) + ssc_msg.data = (ssc_msg.data | DA9052_PDDIS_OUT32KPD); + else + ssc_msg.data = (ssc_msg.data & ~(DA9052_PDDIS_OUT32KPD)); + + ret = priv->da9052->write(priv->da9052, &ssc_msg); + if (ret != 0) { + da9052_unlock(priv->da9052); + goto err_ssc_comm; + } + + /* Set clock trim frequency value */ + ssc_msg.addr = DA9052_OSCTRIM_REG; + ssc_msg.data = priv->set_osc_trim_freq; + + ret = priv->da9052->write(priv->da9052, &ssc_msg); + if (ret != 0) { + da9052_unlock(priv->da9052); + goto err_ssc_comm; + } + da9052_unlock(priv->da9052); + + priv->rtc = rtc_device_register(pdev->name, + &pdev->dev, &da9052_rtc_ops, THIS_MODULE); + if (IS_ERR(priv->rtc)) { + ret = PTR_ERR(priv->rtc); + goto err_ssc_comm; + } + return 0; + +err_ssc_comm: + priv->da9052->unregister_event_notifier + (priv->da9052, &priv->eh_data); +err_register_alarm: + platform_set_drvdata(pdev, NULL); + kfree(priv); + + return ret; +} + +static int __devexit da9052_rtc_remove(struct platform_device *pdev) +{ + struct da9052_rtc *priv = platform_get_drvdata(pdev); + rtc_device_unregister(priv->rtc); + da9052_lock(priv->da9052); + priv->da9052->unregister_event_notifier(priv->da9052, &priv->eh_data); + da9052_unlock(priv->da9052); + platform_set_drvdata(pdev, NULL); + kfree(priv); + return 0; +} + +static struct platform_driver da9052_rtc_driver = { + .probe = da9052_rtc_probe, + .remove = __devexit_p(da9052_rtc_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + + +static int __init da9052_rtc_init(void) +{ + return platform_driver_register(&da9052_rtc_driver); +} +module_init(da9052_rtc_init); + +static void __exit da9052_rtc_exit(void) +{ + platform_driver_unregister(&da9052_rtc_driver); +} +module_exit(da9052_rtc_exit); + +MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com>"); +MODULE_DESCRIPTION("RTC driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/spi/spi_sam.c b/drivers/spi/spi_sam.c new file mode 100644 index 000000000000..a67a01368010 --- /dev/null +++ b/drivers/spi/spi_sam.c @@ -0,0 +1,1161 @@ +/* + * spi_sam.c - Samsung SOC SPI controller driver. + * By -- Jaswinder Singh <jassi.brar@samsung.com> + * + * Copyright (C) 2009 Samsung Electronics Ltd. + */ + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/spi/spi.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <asm/gpio.h> +#include <asm/dma.h> + +#include "spi_sam.h" + +//#define DEBUGSPI + +#ifdef DEBUGSPI + +#define dbg_printk(x...) printk(x) + +static void dump_regs(struct samspi_bus *sspi) +{ + u32 val; + + val = readl(sspi->regs + SAMSPI_CH_CFG); + printk("CHN-%x\t", val); + val = readl(sspi->regs + SAMSPI_CLK_CFG); + printk("CLK-%x\t", val); + val = readl(sspi->regs + SAMSPI_MODE_CFG); + printk("MOD-%x\t", val); + val = readl(sspi->regs + SAMSPI_SLAVE_SEL); + printk("SLVSEL-%x\t", val); + val = readl(sspi->regs + SAMSPI_SPI_STATUS); + if(val & SPI_STUS_TX_DONE) + printk("TX_done\t"); + if(val & SPI_STUS_TRAILCNT_ZERO) + printk("TrailZ\t"); + if(val & SPI_STUS_RX_OVERRUN_ERR) + printk("RX_Ovrn\t"); + if(val & SPI_STUS_RX_UNDERRUN_ERR) + printk("Rx_Unrn\t"); + if(val & SPI_STUS_TX_OVERRUN_ERR) + printk("Tx_Ovrn\t"); + if(val & SPI_STUS_TX_UNDERRUN_ERR) + printk("Tx_Unrn\t"); + if(val & SPI_STUS_RX_FIFORDY) + printk("Rx_Rdy\t"); + if(val & SPI_STUS_TX_FIFORDY) + printk("Tx_Rdy\t"); + printk("Rx/TxLvl=%d,%d\n", (val>>13)&0x7f, (val>>6)&0x7f); +} + +static void dump_spidevice_info(struct spi_device *spi) +{ + dbg_printk("Modalias = %s\n", spi->modalias); + dbg_printk("Slave-%d on Bus-%d\n", spi->chip_select, spi->master->bus_num); + dbg_printk("max_speed_hz = %d\n", spi->max_speed_hz); + dbg_printk("bits_per_word = %d\n", spi->bits_per_word); + dbg_printk("irq = %d\n", spi->irq); + dbg_printk("Clk Phs = %d\n", spi->mode & SPI_CPHA); + dbg_printk("Clk Pol = %d\n", spi->mode & SPI_CPOL); + dbg_printk("ActiveCS = %s\n", (spi->mode & (1<<2)) ? "high" : "low" ); + dbg_printk("Our Mode = %s\n", (spi->mode & SPI_SLAVE) ? "Slave" : "Master"); +} + +#else + +#define dbg_printk(x...) /**/ +#define dump_regs(sspi) /**/ +#define dump_spidevice_info(spi) /**/ + +#endif + +static void dump_spi_regs(struct samspi_bus *sspi) +{ + printk(KERN_CRIT "Reg Info \n"); + printk(KERN_CRIT "CH_CFG = 0x%8.8x\n", readl(sspi->regs + SAMSPI_CH_CFG)); + printk(KERN_CRIT "CLK_CFG = 0x%8.8x\n", readl(sspi->regs + SAMSPI_CLK_CFG)); + printk(KERN_CRIT "MODE_CFG = 0x%8.8x\n", readl(sspi->regs + SAMSPI_MODE_CFG)); + printk(KERN_CRIT "CS_REG = 0x%8.8x\n", readl(sspi->regs + SAMSPI_SLAVE_SEL)); + printk(KERN_CRIT "SPI_INT_EN = 0x%8.8x\n", readl(sspi->regs + SAMSPI_SPI_INT_EN)); + printk(KERN_CRIT "SPI_STATUS = 0x%8.8x\n", readl(sspi->regs + SAMSPI_SPI_STATUS)); +// printk(KERN_CRIT "SAMSPI_SPI_RX_DATA = 0x%8.8x\n", readl(sspi->regs + SAMSPI_SPI_RX_DATA)); +// printk(KERN_CRIT "SAMSPI_SPI_TX_DATA = 0x%8.8x\n", readl(sspi->regs + SAMSPI_SPI_TX_DATA)); +} + +static struct s3c2410_dma_client samspi_dma_client = { + .name = "samspi-dma", +}; + +static int sspi_getclcks(struct samspi_bus *sspi) +{ + struct clk *cspi, *cp, *cm, *cf; + + cp = NULL; + cm = NULL; + cf = NULL; + cspi = sspi->clk; + + if(cspi == NULL){ + cspi = clk_get(&sspi->pdev->dev, "spi"); + if(IS_ERR(cspi)){ + printk("Unable to get spi!\n"); + return -EBUSY; + } + } + dbg_printk("%s:%d Got clk=spi\n", __func__, __LINE__); + +#if defined(CONFIG_SPICLK_SRC_SCLK48M) || defined(CONFIG_SPICLK_SRC_EPLL) || defined(CONFIG_SPICLK_SRC_SPIEXT) + cp = clk_get(&sspi->pdev->dev, spiclk_src); + if(IS_ERR(cp)){ + printk("Unable to get parent clock(%s)!\n", spiclk_src); + if(sspi->clk == NULL){ + clk_disable(cspi); + clk_put(cspi); + } + return -EBUSY; + } + dbg_printk("%s:%d Got clk=%s\n", __func__, __LINE__, spiclk_src); + +#if defined(CONFIG_SPICLK_SRC_EPLL) || defined(CONFIG_SPICLK_SRC_SPIEXT) + cm = clk_get(&sspi->pdev->dev, spisclk_src); + if(IS_ERR(cm)){ + printk("Unable to get %s\n", spisclk_src); + clk_put(cp); + return -EBUSY; + } + dbg_printk("%s:%d Got clk=%s\n", __func__, __LINE__, spisclk_src); + if(clk_set_parent(cp, cm)){ + printk("failed to set %s as the parent of %s\n", spisclk_src, spiclk_src); + clk_put(cm); + clk_put(cp); + return -EBUSY; + } + dbg_printk("Set %s as the parent of %s\n", spisclk_src, spiclk_src); + +#if defined(CONFIG_SPICLK_EPLL_MOUTEPLL) /* MOUTepll through EPLL */ + cf = clk_get(&sspi->pdev->dev, "fout_epll"); + if(IS_ERR(cf)){ + printk("Unable to get fout_epll\n"); + clk_put(cm); + clk_put(cp); + return -EBUSY; + } + dbg_printk("Got fout_epll\n"); + if(clk_set_parent(cm, cf)){ + printk("failed to set FOUTepll as parent of %s\n", spisclk_src); + clk_put(cf); + clk_put(cm); + clk_put(cp); + return -EBUSY; + } + dbg_printk("Set FOUTepll as parent of %s\n", spisclk_src); + clk_put(cf); +#endif + clk_put(cm); +#endif + + sspi->prnt_clk = cp; +#endif + + sspi->clk = cspi; + return 0; +} + +static void sspi_putclcks(struct samspi_bus *sspi) +{ + if(sspi->prnt_clk != NULL) + clk_put(sspi->prnt_clk); + + clk_put(sspi->clk); +} + +static int sspi_enclcks(struct samspi_bus *sspi) +{ + if(sspi->prnt_clk != NULL) + clk_enable(sspi->prnt_clk); + + return clk_enable(sspi->clk); +} + +static void sspi_disclcks(struct samspi_bus *sspi) +{ + if(sspi->prnt_clk != NULL) + clk_disable(sspi->prnt_clk); + + clk_disable(sspi->clk); +} + +static unsigned long sspi_getrate(struct samspi_bus *sspi) +{ + if(sspi->prnt_clk != NULL) + return clk_get_rate(sspi->prnt_clk); + else + return clk_get_rate(sspi->clk); +} + +static int sspi_setrate(struct samspi_bus *sspi, unsigned long r) +{ + /* We don't take charge of the Src Clock, yet */ + return 0; +} + +static inline void enable_spidma(struct samspi_bus *sspi, struct spi_transfer *xfer) +{ + u32 val; + + val = readl(sspi->regs + SAMSPI_MODE_CFG); + val &= ~(SPI_MODE_TXDMA_ON | SPI_MODE_RXDMA_ON); + if(xfer->tx_buf != NULL) + val |= SPI_MODE_TXDMA_ON; + if(xfer->rx_buf != NULL) + val |= SPI_MODE_RXDMA_ON; + writel(val, sspi->regs + SAMSPI_MODE_CFG); +} + +static inline void flush_dma(struct samspi_bus *sspi, struct spi_transfer *xfer) +{ + if(xfer->tx_buf != NULL) + s3c2410_dma_ctrl(sspi->tx_dmach, S3C2410_DMAOP_FLUSH); + if(xfer->rx_buf != NULL) + s3c2410_dma_ctrl(sspi->rx_dmach, S3C2410_DMAOP_FLUSH); +} + +static inline void flush_spi(struct samspi_bus *sspi) +{ + u32 val; + + val = readl(sspi->regs + SAMSPI_CH_CFG); + val |= SPI_CH_SW_RST; + val &= ~SPI_CH_HS_EN; + if((sspi->cur_speed > 30000000UL) && !(sspi->cur_mode & SPI_SLAVE)) /* TODO ??? */ + val |= SPI_CH_HS_EN; + writel(val, sspi->regs + SAMSPI_CH_CFG); + + /* Flush TxFIFO*/ + do{ + val = readl(sspi->regs + SAMSPI_SPI_STATUS); + val = (val>>6) & 0x7f; + }while(val); + + /* Flush RxFIFO*/ + val = readl(sspi->regs + SAMSPI_SPI_STATUS); + val = (val>>13) & 0x7f; + while(val){ + readl(sspi->regs + SAMSPI_SPI_RX_DATA); + val = readl(sspi->regs + SAMSPI_SPI_STATUS); + val = (val>>13) & 0x7f; + } + + val = readl(sspi->regs + SAMSPI_CH_CFG); + val &= ~SPI_CH_SW_RST; + writel(val, sspi->regs + SAMSPI_CH_CFG); +} + +static inline void enable_spichan(struct samspi_bus *sspi, struct spi_transfer *xfer) +{ + u32 val; + + //printk(KERN_CRIT "@@@@@@@@ enable_spichan() \n"); + + val = readl(sspi->regs + SAMSPI_CH_CFG); + val &= ~(SPI_CH_RXCH_ON | SPI_CH_TXCH_ON); + if(xfer->tx_buf != NULL){ + val |= SPI_CH_TXCH_ON; + } + if(xfer->rx_buf != NULL){ + if(!(sspi->cur_mode & SPI_SLAVE)){ + writel((xfer->len & 0xffff) | SPI_PACKET_CNT_EN, + sspi->regs + SAMSPI_PACKET_CNT); /* XXX TODO Bytes or number of SPI-Words? */ + } + val |= SPI_CH_RXCH_ON; + } + writel(val, sspi->regs + SAMSPI_CH_CFG); +} + +static inline void enable_spiintr(struct samspi_bus *sspi, struct spi_transfer *xfer) +{ + u32 val = 0; + + if(xfer->tx_buf != NULL){ + val |= SPI_INT_TX_OVERRUN_EN; + if(!(sspi->cur_mode & SPI_SLAVE)) + val |= SPI_INT_TX_UNDERRUN_EN; + } + if(xfer->rx_buf != NULL){ + val |= (SPI_INT_RX_UNDERRUN_EN | SPI_INT_RX_OVERRUN_EN | SPI_INT_TRAILING_EN); + } + writel(val, sspi->regs + SAMSPI_SPI_INT_EN); +} + +static inline void enable_spienqueue(struct samspi_bus *sspi, struct spi_transfer *xfer) +{ + if(xfer->rx_buf != NULL){ + sspi->rx_done = BUSY; + s3c2410_dma_config(sspi->rx_dmach, sspi->cur_bpw/8, 0); + s3c2410_dma_enqueue(sspi->rx_dmach, (void *)sspi, xfer->rx_dma, xfer->len); + } + if(xfer->tx_buf != NULL){ + sspi->tx_done = BUSY; + s3c2410_dma_config(sspi->tx_dmach, sspi->cur_bpw/8, 0); + s3c2410_dma_enqueue(sspi->tx_dmach, (void *)sspi, xfer->tx_dma, xfer->len); + } +} + +static inline void enable_cs(struct samspi_bus *sspi, struct spi_device *spi) +{ + u32 val; + struct sam_spi_pdata *spd = (struct sam_spi_pdata *)spi->controller_data; + + val = readl(sspi->regs + SAMSPI_SLAVE_SEL); + + if(sspi->cur_mode & SPI_SLAVE){ + val |= SPI_SLAVE_AUTO; /* Auto Mode */ + val |= SPI_SLAVE_SIG_INACT; + }else{ /* Master Mode */ + val &= ~SPI_SLAVE_AUTO; /* Manual Mode */ + val &= ~SPI_SLAVE_SIG_INACT; /* Activate CS */ + if(spi->mode & SPI_CS_HIGH){ // spd->cs_act_high){ + spd->cs_set(spd->cs_pin, CS_HIGH); + spd->cs_level = CS_HIGH; + }else{ + spd->cs_set(spd->cs_pin, CS_LOW); + spd->cs_level = CS_LOW; + } + } + + writel(val, sspi->regs + SAMSPI_SLAVE_SEL); +} + +static inline void disable_cs(struct samspi_bus *sspi, struct spi_device *spi) +{ + u32 val; + struct sam_spi_pdata *spd = (struct sam_spi_pdata *)spi->controller_data; + + if(!(spi->mode & SPI_CS_HIGH) && spd->cs_act_high){ + dbg_printk("%s:%s:%d Slave supports SPI_CS_HIGH, but not requested by Master!\n", __FILE__, __func__, __LINE__); + } + + val = readl(sspi->regs + SAMSPI_SLAVE_SEL); + + if(sspi->cur_mode & SPI_SLAVE){ + val |= SPI_SLAVE_AUTO; /* Auto Mode */ + }else{ /* Master Mode */ + val &= ~SPI_SLAVE_AUTO; /* Manual Mode */ + val |= SPI_SLAVE_SIG_INACT; /* DeActivate CS */ + if(spi->mode & SPI_CS_HIGH){ // spd->cs_act_high){ + spd->cs_set(spd->cs_pin, CS_LOW); + spd->cs_level = CS_LOW; + }else{ + spd->cs_set(spd->cs_pin, CS_HIGH); + spd->cs_level = CS_HIGH; + } + } + + writel(val, sspi->regs + SAMSPI_SLAVE_SEL); +} + +static inline void set_polarity(struct samspi_bus *sspi) +{ + u32 val; + + val = readl(sspi->regs + SAMSPI_CH_CFG); + val &= ~(SPI_CH_SLAVE | SPI_CPOL_L | SPI_CPHA_B); + if(sspi->cur_mode & SPI_SLAVE) + val |= SPI_CH_SLAVE; + if(!(sspi->cur_mode & SPI_CPOL)) + val |= SPI_CPOL_L; + if(sspi->cur_mode & SPI_CPHA) + val |= SPI_CPHA_B; + writel(val, sspi->regs + SAMSPI_CH_CFG); +} + +static inline void set_clock(struct samspi_bus *sspi) +{ + u32 val; + + val = readl(sspi->regs + SAMSPI_CLK_CFG); + val &= ~(SPI_CLKSEL_SRCMSK | SPI_ENCLK_ENABLE | 0xff); + val |= SPI_CLKSEL_SRC; + if(!(sspi->cur_mode & SPI_SLAVE)){ + val |= ((sspi_getrate(sspi) / sspi->cur_speed / 2 - 1) << 0); // PCLK and PSR + val |= SPI_ENCLK_ENABLE; + } + writel(val, sspi->regs + SAMSPI_CLK_CFG); +} + +static inline void set_dmachan(struct samspi_bus *sspi) +{ + u32 val; + + val = readl(sspi->regs + SAMSPI_MODE_CFG); + val &= ~((0x3<<17) | (0x3<<29)); + if(sspi->cur_bpw == 8){ + val |= SPI_MODE_CH_TSZ_BYTE; + val |= SPI_MODE_BUS_TSZ_BYTE; + }else if(sspi->cur_bpw == 16){ + val |= SPI_MODE_CH_TSZ_HALFWORD; + val |= SPI_MODE_BUS_TSZ_HALFWORD; + }else if(sspi->cur_bpw == 32){ + val |= SPI_MODE_CH_TSZ_WORD; + val |= SPI_MODE_BUS_TSZ_WORD; + }else{ + printk("Invalid Bits/Word!\n"); + } + val &= ~(SPI_MODE_4BURST | SPI_MODE_TXDMA_ON | SPI_MODE_RXDMA_ON); + writel(val, sspi->regs + SAMSPI_MODE_CFG); +} + +static void config_sspi(struct samspi_bus *sspi) +{ + /* Set Polarity and Phase */ + set_polarity(sspi); + + /* Set Channel & DMA Mode */ + set_dmachan(sspi); +} + +static void samspi_hwinit(struct samspi_bus *sspi, int channel) +{ + unsigned int val; + + writel(SPI_SLAVE_SIG_INACT, sspi->regs + SAMSPI_SLAVE_SEL); + + /* Disable Interrupts */ + writel(0, sspi->regs + SAMSPI_SPI_INT_EN); + +#ifdef CONFIG_CPU_S3C6410 + writel((readl(S3C64XX_SPC_BASE) & ~(3<<28)) | (3<<28), S3C64XX_SPC_BASE); + writel((readl(S3C64XX_SPC_BASE) & ~(3<<18)) | (3<<18), S3C64XX_SPC_BASE); +#elif defined (CONFIG_CPU_S5P6440) + writel((readl(S5P64XX_SPC_BASE) & ~(3<<28)) | (3<<28), S5P64XX_SPC_BASE); + writel((readl(S5P64XX_SPC_BASE) & ~(3<<18)) | (3<<18), S5P64XX_SPC_BASE); +#elif defined (CONFIG_CPU_S5P6440) + /* How to control drive strength, if we must? */ +#endif + + writel(SPI_CLKSEL_SRC, sspi->regs + SAMSPI_CLK_CFG); + writel(0, sspi->regs + SAMSPI_MODE_CFG); + writel(SPI_SLAVE_SIG_INACT, sspi->regs + SAMSPI_SLAVE_SEL); + writel(0, sspi->regs + SAMSPI_PACKET_CNT); + writel(readl(sspi->regs + SAMSPI_PENDING_CLR), sspi->regs + SAMSPI_PENDING_CLR); + writel(SPI_FBCLK_0NS, sspi->regs + SAMSPI_FB_CLK); + + flush_spi(sspi); + + writel(0, sspi->regs + SAMSPI_SWAP_CFG); + writel(SPI_FBCLK_9NS, sspi->regs + SAMSPI_FB_CLK); + + val = readl(sspi->regs + SAMSPI_MODE_CFG); + val &= ~(SPI_MAX_TRAILCNT << SPI_TRAILCNT_OFF); + if(channel == 0) + SET_MODECFG(val, 0); + else + SET_MODECFG(val, 1); + val |= (SPI_TRAILCNT << SPI_TRAILCNT_OFF); + writel(val, sspi->regs + SAMSPI_MODE_CFG); +} + +static irqreturn_t samspi_interrupt(int irq, void *dev_id) +{ + u32 val; + struct samspi_bus *sspi = (struct samspi_bus *)dev_id; + + dump_regs(sspi); + val = readl(sspi->regs + SAMSPI_PENDING_CLR); + dbg_printk("PENDING=%x\n", val); + writel(val, sspi->regs + SAMSPI_PENDING_CLR); + + /* We get interrupted only for bad news */ + if(sspi->tx_done != PASS){ + printk(KERN_CRIT "TX FAILED \n"); + sspi->tx_done = FAIL; + } + if(sspi->rx_done != PASS){ + printk(KERN_CRIT "RX FAILED \n"); + sspi->rx_done = FAIL; + } + sspi->state = STOPPED; + complete(&sspi->xfer_completion); + + return IRQ_HANDLED; +} + +void samspi_dma_rxcb(struct s3c2410_dma_chan *chan, void *buf_id, int size, enum s3c2410_dma_buffresult res) +{ + struct samspi_bus *sspi = (struct samspi_bus *)buf_id; + + if(res == S3C2410_RES_OK){ + sspi->rx_done = PASS; + dbg_printk("DmaRx-%d ", size); + }else{ + sspi->rx_done = FAIL; + dbg_printk("DmaAbrtRx-%d ", size); + } + + if(sspi->tx_done != BUSY && sspi->state != STOPPED) /* If other done and all OK */ + complete(&sspi->xfer_completion); +} + +void samspi_dma_txcb(struct s3c2410_dma_chan *chan, void *buf_id, int size, enum s3c2410_dma_buffresult res) +{ + struct samspi_bus *sspi = (struct samspi_bus *)buf_id; + + if(res == S3C2410_RES_OK){ + sspi->tx_done = PASS; + dbg_printk("DmaTx-%d ", size); + }else{ + sspi->tx_done = FAIL; + dbg_printk("DmaAbrtTx-%d ", size); + } + + if(sspi->rx_done != BUSY && sspi->state != STOPPED) /* If other done and all OK */ + complete(&sspi->xfer_completion); +} + +static int wait_for_txshiftout(struct samspi_bus *sspi, unsigned long t) +{ + unsigned long timeout; + + timeout = jiffies + t; + while((__raw_readl(sspi->regs + SAMSPI_SPI_STATUS) >> 6) & 0x7f){ + if(time_after(jiffies, timeout)) + return -1; + cpu_relax(); + } + return 0; +} + +static int wait_for_xfer(struct samspi_bus *sspi, struct spi_transfer *xfer) +{ + int status; + u32 val; + + val = msecs_to_jiffies(xfer->len / (sspi->min_speed / 8 / 1000)); /* time to xfer data at min. speed */ + if(sspi->cur_mode & SPI_SLAVE) + val += msecs_to_jiffies(5000); /* 5secs to switch on the Master */ + else + val += msecs_to_jiffies(10); /* just some more */ + status = wait_for_completion_interruptible_timeout(&sspi->xfer_completion, val); + + //printk(KERN_CRIT "Return from waitforcompletion = %d \n", status); + + if(status == 0) + status = -ETIMEDOUT; + else if(status == -ERESTARTSYS) + status = -EINTR; + else if((sspi->tx_done != PASS) || (sspi->rx_done != PASS)) /* Some Xfer failed */ + status = -EIO; + else + status = 0; /* All OK */ + + /* When TxLen <= SPI-FifoLen in Slave mode, DMA returns naively */ + if(!status && (sspi->cur_mode & SPI_SLAVE) && (xfer->tx_buf != NULL)){ + val = msecs_to_jiffies(xfer->len / (sspi->min_speed / 8 / 1000)); /* Be lenient */ + val += msecs_to_jiffies(5000); /* 5secs to switch on the Master */ + status = wait_for_txshiftout(sspi, val); + if(status == -1) + status = -ETIMEDOUT; + else + status = 0; + } + + return status; +} + +#define INVALID_DMA_ADDRESS 0xffffffff +/* First, try to map buf onto phys addr as such. + * If xfer->r/tx_buf was not on contiguous memory, + * allocate from our preallocated DMA buffer. + */ +static int samspi_map_xfer(struct samspi_bus *sspi, struct spi_transfer *xfer) +{ + struct device *dev = &sspi->pdev->dev; + + sspi->rx_tmp = NULL; + sspi->tx_tmp = NULL; + + xfer->tx_dma = xfer->rx_dma = INVALID_DMA_ADDRESS; + if(xfer->tx_buf != NULL){ + xfer->tx_dma = dma_map_single(dev, + (void *) xfer->tx_buf, xfer->len, + DMA_TO_DEVICE); + if(dma_mapping_error(dev, xfer->tx_dma)) + goto alloc_from_buffer; + } + if(xfer->rx_buf != NULL){ + xfer->rx_dma = dma_map_single(dev, + xfer->rx_buf, xfer->len, + DMA_FROM_DEVICE); + if(dma_mapping_error(dev, xfer->rx_dma)){ + if(xfer->tx_buf) + dma_unmap_single(dev, + xfer->tx_dma, xfer->len, + DMA_TO_DEVICE); + goto alloc_from_buffer; + } + } + return 0; + +alloc_from_buffer: /* If the xfer->[r/t]x_buf was not on contiguous memory */ + + printk(KERN_CRIT "############ Allocating from buffer...\n"); + + if(xfer->len <= SAMSPI_DMABUF_LEN){ + if(xfer->rx_buf != NULL){ + xfer->rx_dma = sspi->rx_dma_phys; + sspi->rx_tmp = (void *)sspi->rx_dma_cpu; + } + if(xfer->tx_buf != NULL){ + xfer->tx_dma = sspi->tx_dma_phys; + sspi->tx_tmp = (void *)sspi->tx_dma_cpu; + } + }else{ + dbg_printk("If you plan to use this Xfer size often, increase SAMSPI_DMABUF_LEN\n"); + if(xfer->rx_buf != NULL){ + sspi->rx_tmp = dma_alloc_coherent(&sspi->pdev->dev, SAMSPI_DMABUF_LEN, + &xfer->rx_dma, GFP_KERNEL | GFP_DMA); + if(sspi->rx_tmp == NULL) + return -ENOMEM; + } + if(xfer->tx_buf != NULL){ + sspi->tx_tmp = dma_alloc_coherent(&sspi->pdev->dev, + SAMSPI_DMABUF_LEN, &xfer->tx_dma, GFP_KERNEL | GFP_DMA); + if(sspi->tx_tmp == NULL){ + if(xfer->rx_buf != NULL) + dma_free_coherent(&sspi->pdev->dev, + SAMSPI_DMABUF_LEN, sspi->rx_tmp, xfer->rx_dma); + return -ENOMEM; + } + } + } + + if(xfer->tx_buf != NULL) + memcpy(sspi->tx_tmp, xfer->tx_buf, xfer->len); + + return 0; +} + +static void samspi_unmap_xfer(struct samspi_bus *sspi, struct spi_transfer *xfer) +{ + if((sspi->rx_tmp == NULL) && (sspi->tx_tmp == NULL)) /* if map_single'd */ + return; + + if((xfer->rx_buf != NULL) && (sspi->rx_tmp != NULL)) + memcpy(xfer->rx_buf, sspi->rx_tmp, xfer->len); + + if(xfer->len > SAMSPI_DMABUF_LEN){ + if(xfer->rx_buf != NULL) + dma_free_coherent(&sspi->pdev->dev, SAMSPI_DMABUF_LEN, sspi->rx_tmp, xfer->rx_dma); + if(xfer->tx_buf != NULL) + dma_free_coherent(&sspi->pdev->dev, SAMSPI_DMABUF_LEN, sspi->tx_tmp, xfer->tx_dma); + }else{ + sspi->rx_tmp = NULL; + sspi->tx_tmp = NULL; + } +} + +static void handle_msg(struct samspi_bus *sspi, struct spi_message *msg) +{ + u8 bpw; + u32 speed, val; + int status = 0; + struct spi_transfer *xfer; + struct spi_device *spi = msg->spi; + + config_sspi(sspi); + + list_for_each_entry (xfer, &msg->transfers, transfer_list) { + + if(!msg->is_dma_mapped && samspi_map_xfer(sspi, xfer)){ + dev_err(&spi->dev, "Xfer: Unable to allocate DMA buffer!\n"); + status = -ENOMEM; + goto out; + } + + INIT_COMPLETION(sspi->xfer_completion); + + /* Only BPW and Speed may change across transfers */ + bpw = xfer->bits_per_word ? : spi->bits_per_word; + speed = xfer->speed_hz ? : spi->max_speed_hz; + + if(sspi->cur_bpw != bpw || sspi->cur_speed != speed){ + sspi->cur_bpw = bpw; + sspi->cur_speed = speed; + config_sspi(sspi); + } + + /* Pending only which is to be done */ + sspi->rx_done = PASS; + sspi->tx_done = PASS; + sspi->state = RUNNING; + + /* Configure Clock */ + set_clock(sspi); + + /* Enable Interrupts */ + enable_spiintr(sspi, xfer); + + if(!(sspi->cur_mode & SPI_SLAVE)) + flush_spi(sspi); + + /* Enqueue data on DMA */ + enable_spienqueue(sspi, xfer); + + /* Enable DMA */ + enable_spidma(sspi, xfer); + + /* Enable TX/RX */ + enable_spichan(sspi, xfer); + + /* Slave Select */ + enable_cs(sspi, spi); + + status = wait_for_xfer(sspi, xfer); + + /************** + * Block Here * + **************/ + + if(status == -ETIMEDOUT){ + dev_err(&spi->dev, "Xfer: Timeout!\n"); + dump_regs(sspi); + sspi->state = STOPPED; + /* DMA Disable*/ + val = readl(sspi->regs + SAMSPI_MODE_CFG); + val &= ~(SPI_MODE_TXDMA_ON | SPI_MODE_RXDMA_ON); + writel(val, sspi->regs + SAMSPI_MODE_CFG); + flush_dma(sspi, xfer); + flush_spi(sspi); + if(!msg->is_dma_mapped) + samspi_unmap_xfer(sspi, xfer); + goto out; + } + if(status == -EINTR){ + dev_err(&spi->dev, "Xfer: Interrupted!\n"); + dump_regs(sspi); + sspi->state = STOPPED; + /* DMA Disable*/ + val = readl(sspi->regs + SAMSPI_MODE_CFG); + val &= ~(SPI_MODE_TXDMA_ON | SPI_MODE_RXDMA_ON); + writel(val, sspi->regs + SAMSPI_MODE_CFG); + flush_dma(sspi, xfer); + flush_spi(sspi); + if(!msg->is_dma_mapped) + samspi_unmap_xfer(sspi, xfer); + goto out; + } + if(status == -EIO){ /* Some Xfer failed */ + dev_err(&spi->dev, "Xfer: Failed!\n"); + dump_regs(sspi); + sspi->state = STOPPED; + /* DMA Disable*/ + val = readl(sspi->regs + SAMSPI_MODE_CFG); + val &= ~(SPI_MODE_TXDMA_ON | SPI_MODE_RXDMA_ON); + writel(val, sspi->regs + SAMSPI_MODE_CFG); + flush_dma(sspi, xfer); + flush_spi(sspi); + if(!msg->is_dma_mapped) + samspi_unmap_xfer(sspi, xfer); + goto out; + } + + if(xfer->delay_usecs){ + udelay(xfer->delay_usecs); + dbg_printk("%s:%s:%d Unverified Control Flow Path!\n", __FILE__, __func__, __LINE__); + } + + if(xfer->cs_change && !(sspi->cur_mode & SPI_SLAVE)){ + disable_cs(sspi, spi); + dbg_printk("%s:%s:%d Unverified Control Flow Path!\n", __FILE__, __func__, __LINE__); + } + + msg->actual_length += xfer->len; + + if(!msg->is_dma_mapped) + samspi_unmap_xfer(sspi, xfer); + + } + +out: + /* Slave Deselect */ + if(!(sspi->cur_mode & SPI_SLAVE)) + disable_cs(sspi, spi); + + /* Disable Interrupts */ + writel(0, sspi->regs + SAMSPI_SPI_INT_EN); + + /* Tx/Rx Disable */ + val = readl(sspi->regs + SAMSPI_CH_CFG); + val &= ~(SPI_CH_RXCH_ON | SPI_CH_TXCH_ON); + writel(val, sspi->regs + SAMSPI_CH_CFG); + + /* DMA Disable*/ + val = readl(sspi->regs + SAMSPI_MODE_CFG); + val &= ~(SPI_MODE_TXDMA_ON | SPI_MODE_RXDMA_ON); + writel(val, sspi->regs + SAMSPI_MODE_CFG); + + msg->status = status; + if(msg->complete) + msg->complete(msg->context); +} + +static void samspi_work(struct work_struct *work) +{ + struct samspi_bus *sspi = container_of(work, struct samspi_bus, work); + unsigned long flags; + + spin_lock_irqsave(&sspi->lock, flags); + while (!list_empty(&sspi->queue)) { + struct spi_message *msg; + + msg = container_of(sspi->queue.next, struct spi_message, queue); + list_del_init(&msg->queue); + spin_unlock_irqrestore(&sspi->lock, flags); + + handle_msg(sspi, msg); + + spin_lock_irqsave(&sspi->lock, flags); + } + spin_unlock_irqrestore(&sspi->lock, flags); +} + +static void samspi_cleanup(struct spi_device *spi) +{ + dbg_printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); +} + +static int samspi_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct spi_master *master = spi->master; + struct samspi_bus *sspi = spi_master_get_devdata(master); + unsigned long flags; + + spin_lock_irqsave(&sspi->lock, flags); + msg->actual_length = 0; + list_add_tail(&msg->queue, &sspi->queue); + queue_work(sspi->workqueue, &sspi->work); + spin_unlock_irqrestore(&sspi->lock, flags); + + return 0; +} + +/* the spi->mode bits understood by this driver: */ +#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_SLAVE | (spd->cs_act_high ? SPI_CS_HIGH : 0) ) +/* + * Here we only check the validity of requested configuration and + * save the configuration in a local data-structure. + * The controller is actually configured only just before + * we get a message to transfer _and_ if no other message is pending(already configured). + */ +static int samspi_setup(struct spi_device *spi) +{ + unsigned long flags; + unsigned int psr; + struct samspi_bus *sspi = spi_master_get_devdata(spi->master); + struct sam_spi_pdata *spd = (struct sam_spi_pdata *)spi->controller_data; + + spin_lock_irqsave(&sspi->lock, flags); + if(!list_empty(&sspi->queue)){ /* Any pending message? */ + spin_unlock_irqrestore(&sspi->lock, flags); + dev_dbg(&spi->dev, "setup: attempt while messages in queue!\n"); + return -EBUSY; + } + spin_unlock_irqrestore(&sspi->lock, flags); + + if (spi->chip_select > spi->master->num_chipselect) { + dev_dbg(&spi->dev, "setup: invalid chipselect %u (%u defined)\n", + spi->chip_select, spi->master->num_chipselect); + return -EINVAL; + } + + spi->bits_per_word = spi->bits_per_word ? : 8; + + if((spi->bits_per_word != 8) && + (spi->bits_per_word != 16) && + (spi->bits_per_word != 32)){ + dev_err(&spi->dev, "setup: %dbits/wrd not supported!\n", spi->bits_per_word); + return -EINVAL; + } + + spi->max_speed_hz = spi->max_speed_hz ? : sspi->max_speed; + + /* Round-off max_speed_hz */ + psr = sspi_getrate(sspi) / spi->max_speed_hz / 2 - 1; + psr &= 0xff; + if(spi->max_speed_hz < sspi_getrate(sspi) / 2 / (psr + 1)) + psr = (psr+1) & 0xff; + + spi->max_speed_hz = sspi_getrate(sspi) / 2 / (psr + 1); + + if (spi->max_speed_hz > sspi->max_speed + || spi->max_speed_hz < sspi->min_speed){ + dev_err(&spi->dev, "setup: req speed(%u) out of range[%u-%u]\n", + spi->max_speed_hz, sspi->min_speed, sspi->max_speed); + return -EINVAL; + } + + if (spi->mode & ~MODEBITS) { + dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n", spi->mode & ~MODEBITS); + return -EINVAL; + } + + if(!(spi->mode & SPI_SLAVE) && (spd->cs_level == CS_FLOAT)){ + spd->cs_config(spd->cs_pin, spd->cs_mode, (spi->mode & SPI_CS_HIGH)/*spd->cs_act_high*/ ? CS_LOW : CS_HIGH); + disable_cs(sspi, spi); + } + + if((sspi->cur_bpw == spi->bits_per_word) && + (sspi->cur_speed == spi->max_speed_hz) && + (sspi->cur_mode == spi->mode)) /* If no change in configuration, do nothing */ + return 0; + + sspi->cur_bpw = spi->bits_per_word; + sspi->cur_speed = spi->max_speed_hz; + sspi->cur_mode = spi->mode; + + SAM_SETGPIOPULL(sspi); + return 0; +} + +static int __init samspi_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct samspi_bus *sspi; + int ret = -ENODEV; + + dbg_printk("%s:%s:%d ID=%d\n", __FILE__, __func__, __LINE__, pdev->id); + master = spi_alloc_master(&pdev->dev, sizeof(struct samspi_bus)); /* Allocate contiguous SPI controller */ + if (master == NULL) + return ret; + sspi = spi_master_get_devdata(master); + sspi->pdev = pdev; + sspi->spi_mstinfo = (struct sam_spi_mstr_info *)pdev->dev.platform_data; + sspi->master = master; + platform_set_drvdata(pdev, master); + + INIT_WORK(&sspi->work, samspi_work); + spin_lock_init(&sspi->lock); + INIT_LIST_HEAD(&sspi->queue); + init_completion(&sspi->xfer_completion); + + ret = sspi_getclcks(sspi); + if(ret){ + dev_err(&pdev->dev, "cannot acquire clock \n"); + ret = -EBUSY; + goto lb1; + } + ret = sspi_enclcks(sspi); + if(ret){ + dev_err(&pdev->dev, "cannot enable clock \n"); + ret = -EBUSY; + goto lb2; + } + + sspi->max_speed = sspi_getrate(sspi) / 2 / (0x0 + 1); + sspi->min_speed = sspi_getrate(sspi) / 2 / (0xff + 1); + + sspi->cur_bpw = 8; + sspi->cur_mode = SPI_SLAVE; /* Start in Slave mode */ + sspi->cur_speed = sspi->min_speed; + + /* Get and Map Resources */ + sspi->iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (sspi->iores == NULL) { + dev_err(&pdev->dev, "cannot find IO resource\n"); + ret = -ENOENT; + goto lb3; + } + + sspi->ioarea = request_mem_region(sspi->iores->start, sspi->iores->end - sspi->iores->start + 1, pdev->name); + if (sspi->ioarea == NULL) { + dev_err(&pdev->dev, "cannot request IO\n"); + ret = -ENXIO; + goto lb4; + } + + sspi->regs = ioremap(sspi->iores->start, sspi->iores->end - sspi->iores->start + 1); + if (sspi->regs == NULL) { + dev_err(&pdev->dev, "cannot map IO\n"); + ret = -ENXIO; + goto lb5; + } + + sspi->tx_dma_cpu = dma_alloc_coherent(&pdev->dev, SAMSPI_DMABUF_LEN, &sspi->tx_dma_phys, GFP_KERNEL | GFP_DMA); + if(sspi->tx_dma_cpu == NULL){ + dev_err(&pdev->dev, "Unable to allocate TX DMA buffers\n"); + ret = -ENOMEM; + goto lb6; + } + + sspi->rx_dma_cpu = dma_alloc_coherent(&pdev->dev, SAMSPI_DMABUF_LEN, &sspi->rx_dma_phys, GFP_KERNEL | GFP_DMA); + if(sspi->rx_dma_cpu == NULL){ + dev_err(&pdev->dev, "Unable to allocate RX DMA buffers\n"); + ret = -ENOMEM; + goto lb7; + } + + sspi->irqres = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if(sspi->irqres == NULL){ + dev_err(&pdev->dev, "cannot find IRQ\n"); + ret = -ENOENT; + goto lb8; + } + + ret = request_irq(sspi->irqres->start, samspi_interrupt, IRQF_DISABLED, + pdev->name, sspi); + if(ret){ + dev_err(&pdev->dev, "cannot acquire IRQ\n"); + ret = -EBUSY; + goto lb9; + } + + sspi->workqueue = create_singlethread_workqueue(master->dev.parent->bus_id); + if(!sspi->workqueue){ + dev_err(&pdev->dev, "cannot create workqueue\n"); + ret = -EBUSY; + goto lb10; + } + + master->bus_num = pdev->id; + master->setup = samspi_setup; + master->transfer = samspi_transfer; + master->cleanup = samspi_cleanup; + master->num_chipselect = sspi->spi_mstinfo->num_slaves; + + if(spi_register_master(master)){ + dev_err(&pdev->dev, "cannot register SPI master\n"); + ret = -EBUSY; + goto lb11; + } + + /* Configure GPIOs */ + if(pdev->id == 0) + SETUP_SPI(sspi, 0); + else if(pdev->id == 1) + SETUP_SPI(sspi, 1); + SAM_SETGPIOPULL(sspi); + + if(s3c2410_dma_request(sspi->rx_dmach, &samspi_dma_client, NULL)){ + dev_err(&pdev->dev, "cannot get RxDMA\n"); + ret = -EBUSY; + goto lb12; + } + s3c2410_dma_set_buffdone_fn(sspi->rx_dmach, samspi_dma_rxcb); + s3c2410_dma_devconfig(sspi->rx_dmach, S3C2410_DMASRC_HW, 0, sspi->sfr_phyaddr + SAMSPI_SPI_RX_DATA); + s3c2410_dma_config(sspi->rx_dmach, sspi->cur_bpw/8, 0); + s3c2410_dma_setflags(sspi->rx_dmach, S3C2410_DMAF_AUTOSTART); + + if(s3c2410_dma_request(sspi->tx_dmach, &samspi_dma_client, NULL)){ + dev_err(&pdev->dev, "cannot get TxDMA\n"); + ret = -EBUSY; + goto lb13; + } + s3c2410_dma_set_buffdone_fn(sspi->tx_dmach, samspi_dma_txcb); + s3c2410_dma_devconfig(sspi->tx_dmach, S3C2410_DMASRC_MEM, 0, sspi->sfr_phyaddr + SAMSPI_SPI_TX_DATA); + s3c2410_dma_config(sspi->tx_dmach, sspi->cur_bpw/8, 0); + s3c2410_dma_setflags(sspi->tx_dmach, S3C2410_DMAF_AUTOSTART); + + /* Setup Deufult Mode */ + samspi_hwinit(sspi, pdev->id); + + printk("Samsung SoC SPI Driver loaded for Bus SPI-%d with %d Slaves attached\n", pdev->id, master->num_chipselect); + printk("\tMax,Min-Speed [%d, %d]Hz\n", sspi->max_speed, sspi->min_speed); + printk("\tIrq=%d\tIOmem=[0x%x-0x%x]\tDMA=[Rx-%d, Tx-%d]\n", + sspi->irqres->start, + sspi->iores->end, sspi->iores->start, + sspi->rx_dmach, sspi->tx_dmach); + + return 0; + +lb13: + s3c2410_dma_free(sspi->rx_dmach, &samspi_dma_client); +lb12: + spi_unregister_master(master); +lb11: + destroy_workqueue(sspi->workqueue); +lb10: + free_irq(sspi->irqres->start, sspi); +lb9: +lb8: + dma_free_coherent(&pdev->dev, SAMSPI_DMABUF_LEN, sspi->rx_dma_cpu, sspi->rx_dma_phys); +lb7: + dma_free_coherent(&pdev->dev, SAMSPI_DMABUF_LEN, sspi->tx_dma_cpu, sspi->tx_dma_phys); +lb6: + iounmap((void *) sspi->regs); +lb5: + release_mem_region(sspi->iores->start, sspi->iores->end - sspi->iores->start + 1); +lb4: +lb3: + sspi_disclcks(sspi); +lb2: + sspi_putclcks(sspi); +lb1: + platform_set_drvdata(pdev, NULL); + spi_master_put(master); + + return ret; +} + +static int __exit samspi_remove(struct platform_device *pdev) +{ + struct spi_master *master = spi_master_get(platform_get_drvdata(pdev)); + struct samspi_bus *sspi = spi_master_get_devdata(master); + + s3c2410_dma_free(sspi->tx_dmach, &samspi_dma_client); + s3c2410_dma_free(sspi->rx_dmach, &samspi_dma_client); + spi_unregister_master(master); + destroy_workqueue(sspi->workqueue); + free_irq(sspi->irqres->start, sspi); + dma_free_coherent(&pdev->dev, SAMSPI_DMABUF_LEN, sspi->rx_dma_cpu, sspi->rx_dma_phys); + dma_free_coherent(&pdev->dev, SAMSPI_DMABUF_LEN, sspi->tx_dma_cpu, sspi->tx_dma_phys); + iounmap((void *) sspi->regs); + release_mem_region(sspi->iores->start, sspi->iores->end - sspi->iores->start + 1); + sspi_disclcks(sspi); + sspi_putclcks(sspi); + platform_set_drvdata(pdev, NULL); + spi_master_put(master); + + return 0; +} + +static struct platform_driver sam_spi_driver = { + .driver = { + .name = "sam-spi", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, +// .remove = sam_spi_remove, +// .shutdown = sam_spi_shutdown, +// .suspend = sam_spi_suspend, +// .resume = sam_spi_resume, +}; + +static int __init sam_spi_init(void) +{ + dbg_printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); + return platform_driver_probe(&sam_spi_driver, samspi_probe); +} +//module_init(sam_spi_init); +subsys_initcall(sam_spi_init); + +static void __exit sam_spi_exit(void) +{ + platform_driver_unregister(&sam_spi_driver); +} +module_exit(sam_spi_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jaswinder Singh Brar <jassi.brar@samsung.com>"); +MODULE_DESCRIPTION("Samsung SOC SPI Controller"); diff --git a/drivers/video/backlight/da9052_bl.c b/drivers/video/backlight/da9052_bl.c new file mode 100644 index 000000000000..060cd894a267 --- /dev/null +++ b/drivers/video/backlight/da9052_bl.c @@ -0,0 +1,455 @@ +/* + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * 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. + * + * da9052_bl.c: Backlight driver for DA9052 + */ + +#include <linux/platform_device.h> +#include <linux/fb.h> +#include <linux/backlight.h> + +#include <linux/delay.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/bl.h> + + +#define DRIVER_NAME "da9052-backlight" +#define DRIVER_NAME1 "WLED-1" +#define DRIVER_NAME2 "WLED-2" +#define DRIVER_NAME3 "WLED-3" + +/* These flags define if Backlight LEDs are present */ +/* Set the following macros to 1, if LEDs are present. Otherwise set to 0 */ +#define DA9052_LED1_PRESENT 1 +#define DA9052_LED2_PRESENT 1 +#define DA9052_LED3_PRESENT 1 + +#define DA9052_MAX_BRIGHTNESS 0xFF + +struct da9052_backlight_data { + struct device *da9052_dev; + int current_brightness; + struct da9052 *da9052; + + int is_led1_present; + int is_led2_present; + int is_led3_present; +}; + +enum da9052_led_number { + LED1 = 1, + LED2, + LED3, +}; + +static int da9052_backlight_brightness_set(struct da9052_backlight_data *data, + int brightness, enum da9052_led_number led) +{ + /* + * Mechanism for brightness control: + * For brightness control, current is used. + * PWM feature is not used. + * To use PWM feature, a fixed value of current should be defined. + */ + + int ret = 0; + unsigned int led_ramp_bit; + unsigned int led_current_register; + unsigned int led_current_sink_bit; + unsigned int led_boost_en_bit; + struct da9052_ssc_msg msg; + + switch (led) { + case LED1: + led_ramp_bit = DA9052_LEDCONT_LED1RAMP; + led_current_register = DA9052_LED1CONF_REG; + led_current_sink_bit = DA9052_LEDCONT_LED1EN; + led_boost_en_bit = DA9052_BOOST_LED1INEN; + break; + case LED2: + led_ramp_bit = DA9052_LEDCONT_LED2RAMP; + led_current_register = DA9052_LED2CONF_REG; + led_current_sink_bit = DA9052_LEDCONT_LED2EN; + led_boost_en_bit = DA9052_BOOST_LED2INEN; + break; + case LED3: + led_ramp_bit = DA9052_LEDCONT_LED3RAMP; + led_current_register = DA9052_LED3CONF_REG; + led_current_sink_bit = DA9052_LEDCONT_LED3EN; + led_boost_en_bit = DA9052_BOOST_LED3INEN; + break; + default: + return -EIO; + } + + /* + 1. Configure the boost register + 2. Configure the LED _CONT register + 3. Configure the LEDx_CONF registers to the brightness value. + */ + msg.addr = DA9052_BOOST_REG; + msg.data = 0x3F; + if (brightness) { + da9052_lock(data->da9052); + ret = data->da9052->write(data->da9052, &msg); + if (ret) { + da9052_unlock(data->da9052); + return ret; + } + da9052_unlock(data->da9052); + } + + msg.addr = DA9052_LEDCONT_REG; + msg.data = 0xFF; + if (brightness) { + da9052_lock(data->da9052); + ret = data->da9052->write(data->da9052, &msg); + if (ret) { + da9052_unlock(data->da9052); + return ret; + } + da9052_unlock(data->da9052); + } + + msg.addr = led_current_register; + msg.data = 0; + /* Write to the DA9052 register */ + da9052_lock(data->da9052); + ret = data->da9052->write(data->da9052, &msg); + if (ret) { + da9052_unlock(data->da9052); + return ret; + } + da9052_unlock(data->da9052); + msleep(20); + msg.data = brightness; + /* Write to the DA9052 register */ + da9052_lock(data->da9052); + ret = data->da9052->write(data->da9052, &msg); + if (ret) { + da9052_unlock(data->da9052); + return ret; + } + da9052_unlock(data->da9052); + + return 0; +} + +static int da9052_backlight_set(struct backlight_device *bl, int brightness) +{ + struct da9052_backlight_data *data = bl_get_data(bl); + int ret = 0; + /* Check for LED1 */ + if (1 == data->is_led1_present) { + ret = da9052_backlight_brightness_set(data, brightness, LED1); + if (ret) + return ret; + } + /* Check for LED2 */ + if (1 == data->is_led2_present) { + ret = da9052_backlight_brightness_set(data, brightness, LED2); + if (ret) + return ret; + } + /* Check for LED3 */ + if (1 == data->is_led3_present) { + ret = da9052_backlight_brightness_set(data, brightness, LED3); + if (ret) + return ret; + } + + data->current_brightness = brightness; + return 0; +} + +static int da9052_init_WLED(struct da9052_backlight_data *data, + enum da9052_led_number led) +{ + int ret = 0; + unsigned int led_current_register; + struct da9052_ssc_msg msg; + + switch (led) { + case LED1: + led_current_register = DA9052_LED1CONF_REG; + break; + case LED2: + led_current_register = DA9052_LED2CONF_REG; + break; + case LED3: + led_current_register = DA9052_LED3CONF_REG; + break; + default: + return -EIO; + } + + msg.addr = DA9052_BOOST_REG; + msg.data = 0x00; + da9052_lock(data->da9052); + ret = data->da9052->write(data->da9052, &msg); + if (ret) { + da9052_unlock(data->da9052); + return ret; + } + da9052_unlock(data->da9052); + + msg.addr = DA9052_LEDCONT_REG; + msg.data = 0x00; + da9052_lock(data->da9052); + ret = data->da9052->write(data->da9052, &msg); + if (ret) { + da9052_unlock(data->da9052); + return ret; + } + da9052_unlock(data->da9052); + + msg.addr = led_current_register; + msg.data = 0; + da9052_lock(data->da9052); + ret = data->da9052->write(data->da9052, &msg); + da9052_unlock(data->da9052); + return ret; +} + +static int da9052_backlight_update_status(struct backlight_device *bl) +{ + int brightness = bl->props.brightness; + + if (bl->props.power != FB_BLANK_UNBLANK) + brightness = 0; + + if (bl->props.fb_blank != FB_BLANK_UNBLANK) + brightness = 0; + return da9052_backlight_set(bl, brightness); +} + +static int da9052_backlight_get_brightness(struct backlight_device *bl) +{ + struct da9052_backlight_data *data = bl_get_data(bl); + return data->current_brightness; +} + +struct backlight_ops da9052_backlight_ops = { + .update_status = da9052_backlight_update_status, + .get_brightness = da9052_backlight_get_brightness, +}; + +static int da9052_backlight_probe1(struct platform_device *pdev) +{ + struct da9052_backlight_data *data; + struct backlight_device *bl; + int ret = 0; + struct da9052 *da9052 = dev_get_drvdata(pdev->dev.parent); + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + data->da9052_dev = pdev->dev.parent; + data->da9052 = da9052; + data->current_brightness = 0; + data->is_led1_present = DA9052_LED1_PRESENT; + + /* Init the WLED-1 bank */ + ret = da9052_init_WLED(data, LED1); + if (ret) + return ret; + + bl = backlight_device_register(pdev->name, data->da9052_dev, + data, &da9052_backlight_ops); + if (IS_ERR(bl)) { + dev_err(&pdev->dev, "failed to register backlight\n"); + kfree(data); + return PTR_ERR(bl); + } + + bl->props.max_brightness = DA9052_MAX_BRIGHTNESS; + bl->props.brightness = 0; + + platform_set_drvdata(pdev, bl); + backlight_update_status(bl); + + return 0; +} +static int da9052_backlight_probe2(struct platform_device *pdev) +{ + struct da9052_backlight_data *data; + struct backlight_device *bl; + struct da9052 *da9052 = dev_get_drvdata(pdev->dev.parent); + int ret = 0; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + data->da9052_dev = pdev->dev.parent; + data->da9052 = da9052; + data->current_brightness = 0; + data->is_led2_present = DA9052_LED2_PRESENT; + + /* Init the WLED-2 bank */ + ret = da9052_init_WLED(data, LED2); + if (ret) + return ret; + + bl = backlight_device_register(pdev->name, data->da9052_dev, + data, &da9052_backlight_ops); + if (IS_ERR(bl)) { + dev_err(&pdev->dev, "failed to register backlight\n"); + kfree(data); + return PTR_ERR(bl); + } + + bl->props.max_brightness = DA9052_MAX_BRIGHTNESS; + bl->props.brightness = 0; + + platform_set_drvdata(pdev, bl); + backlight_update_status(bl); + + return 0; +} +static int da9052_backlight_probe3(struct platform_device *pdev) +{ + struct da9052_backlight_data *data; + struct backlight_device *bl; + struct da9052 *da9052 = dev_get_drvdata(pdev->dev.parent); + int ret = 0; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + data->da9052_dev = pdev->dev.parent; + data->da9052 = da9052; + data->current_brightness = 0; + data->is_led3_present = DA9052_LED3_PRESENT; + + /* Init the WLED-2 bank */ + ret = da9052_init_WLED(data, LED3); + if (ret) + return ret; + + bl = backlight_device_register(pdev->name, data->da9052_dev, + data, &da9052_backlight_ops); + if (IS_ERR(bl)) { + dev_err(&pdev->dev, "failed to register backlight\n"); + kfree(data); + return PTR_ERR(bl); + } + + bl->props.max_brightness = DA9052_MAX_BRIGHTNESS; + bl->props.brightness = 0; + + platform_set_drvdata(pdev, bl); + + backlight_update_status(bl); + return 0; +} + +static int da9052_backlight_remove1(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + struct da9052_backlight_data *data = bl_get_data(bl); + int ret = 0; + + /* Switch off the WLED-1 */ + ret = da9052_init_WLED(data, LED1); + if (ret) + return ret; + + backlight_device_unregister(bl); + kfree(data); + return 0; +} + +static int da9052_backlight_remove2(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + struct da9052_backlight_data *data = bl_get_data(bl); + int ret = 0; + + /* Switch off the WLED-2 */ + ret = da9052_init_WLED(data, LED2); + if (ret) + return ret; + + backlight_device_unregister(bl); + kfree(data); + return 0; +} +static int da9052_backlight_remove3(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + struct da9052_backlight_data *data = bl_get_data(bl); + int ret; + + /* Switch off the WLED-3 */ + ret = da9052_init_WLED(data, LED3); + if (ret) + return ret; + + backlight_device_unregister(bl); + kfree(data); + return 0; +} + +static struct platform_driver da9052_backlight_driver1 = { + .driver = { + .name = DRIVER_NAME1, + .owner = THIS_MODULE, + }, + .probe = da9052_backlight_probe1, + .remove = da9052_backlight_remove1, +}; +static struct platform_driver da9052_backlight_driver2 = { + .driver = { + .name = DRIVER_NAME2, + .owner = THIS_MODULE, + }, + .probe = da9052_backlight_probe2, + .remove = da9052_backlight_remove2, +}; +static struct platform_driver da9052_backlight_driver3 = { + .driver = { + .name = DRIVER_NAME3, + .owner = THIS_MODULE, + }, + .probe = da9052_backlight_probe3, + .remove = da9052_backlight_remove3, +}; + +static int __init da9052_backlight_init(void) +{ + s32 ret; + ret = platform_driver_register(&da9052_backlight_driver1); + if (ret) + return ret; + ret = platform_driver_register(&da9052_backlight_driver2); + if (ret) + return ret; + + ret = platform_driver_register(&da9052_backlight_driver3); + if (ret) + return ret; + + return ret; +} +module_init(da9052_backlight_init); + +static void __exit da9052_backlight_exit(void) +{ + platform_driver_unregister(&da9052_backlight_driver1); + platform_driver_unregister(&da9052_backlight_driver2); + platform_driver_unregister(&da9052_backlight_driver3); +} +module_exit(da9052_backlight_exit); + +MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com>"); +MODULE_DESCRIPTION("Backlight driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); + diff --git a/drivers/watchdog/da9052_wdt.c b/drivers/watchdog/da9052_wdt.c new file mode 100644 index 000000000000..11e1d75212c3 --- /dev/null +++ b/drivers/watchdog/da9052_wdt.c @@ -0,0 +1,542 @@ +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/uaccess.h> +#include <linux/jiffies.h> +#include <linux/platform_device.h> +#include <linux/time.h> +#include <linux/watchdog.h> +#include <linux/types.h> +#include <linux/kernel.h> + + +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/wdt.h> + +#define DRIVER_NAME "da9052-wdt" + +#define DA9052_STROBING_FILTER_ENABLE 0x0001 +#define DA9052_STROBING_FILTER_DISABLE 0x0002 +#define DA9052_SET_STROBING_MODE_MANUAL 0x0004 +#define DA9052_SET_STROBING_MODE_AUTO 0x0008 + +#define KERNEL_MODULE 0 +#define ENABLE 1 +#define DISABLE 0 + +static u8 sm_strobe_filter_flag = DISABLE; +static u8 sm_strobe_mode_flag = DA9052_STROBE_MANUAL; +static u32 sm_mon_interval = DA9052_ADC_TWDMIN_TIME; +static u8 sm_str_req = DISABLE; +static u8 da9052_sm_scale = DA9052_WDT_DISABLE; +module_param(sm_strobe_filter_flag, byte, 0); +MODULE_PARM_DESC(sm_strobe_filter_flag, + "DA9052 SM driver strobe filter flag default = DISABLE"); + +module_param(sm_strobe_mode_flag, byte, 0); +MODULE_PARM_DESC(sm_strobe_mode_flag, + "DA9052 SM driver watchdog strobing mode default\ + = DA9052_STROBE_MANUAL"); + +module_param(da9052_sm_scale, byte, 0); +MODULE_PARM_DESC(da9052_sm_scale, + "DA9052 SM driver scaling value used to calculate the\ + time for the strobing filter default = 0"); + +module_param(sm_str_req, byte, 0); +MODULE_PARM_DESC(sm_str_req, + "DA9052 SM driver strobe request flag default = DISABLE"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static struct timer_list *monitoring_timer; + +struct da9052_wdt { + struct platform_device *pdev; + struct da9052 *da9052; +}; +static struct miscdevice da9052_wdt_miscdev; +static unsigned long da9052_wdt_users; +static int da9052_wdt_expect_close; + +static struct da9052_wdt *get_wdt_da9052(void) +{ + /*return dev_get_drvdata(da9052_wdt_miscdev.parent);*/ + return platform_get_drvdata + (to_platform_device(da9052_wdt_miscdev.parent)); +} + +void start_strobing(struct work_struct *work) +{ + struct da9052_ssc_msg msg; + int ret; + struct da9052_wdt *wdt = get_wdt_da9052(); + + + if (NULL == wdt) { + mod_timer(monitoring_timer, jiffies + sm_mon_interval); + return; + } + msg.addr = DA9052_CONTROLD_REG; + msg.data = 0; + da9052_lock(wdt->da9052); + ret = wdt->da9052->read(wdt->da9052, &msg); + if (ret) { + da9052_unlock(wdt->da9052); + return; + } + da9052_unlock(wdt->da9052); + + msg.data = (msg.data | DA9052_CONTROLD_WATCHDOG); + da9052_lock(wdt->da9052); + ret = wdt->da9052->write(wdt->da9052, &msg); + if (ret) { + da9052_unlock(wdt->da9052); + return; + } + da9052_unlock(wdt->da9052); + + sm_str_req = DISABLE; + + mod_timer(monitoring_timer, jiffies + sm_mon_interval); + return; +} + + +void timer_callback(void) +{ + if (((sm_strobe_mode_flag) && + (sm_strobe_mode_flag == DA9052_STROBE_MANUAL)) || + (sm_strobe_mode_flag == DA9052_STROBE_AUTO)) { + schedule_work(&strobing_action); + } else { + if (sm_strobe_mode_flag == DA9052_STROBE_MANUAL) { + mod_timer(monitoring_timer, jiffies + + sm_mon_interval); + } + } +} + +static int da9052_sm_hw_init(struct da9052_wdt *wdt) +{ + /* Create timer structure */ + monitoring_timer = kzalloc(sizeof(struct timer_list), GFP_KERNEL); + if (!monitoring_timer) + return -ENOMEM; + + init_timer(monitoring_timer); + monitoring_timer->expires = jiffies + sm_mon_interval; + monitoring_timer->function = (void *)&timer_callback; + + sm_strobe_filter_flag = DA9052_SM_STROBE_CONF; + sm_strobe_mode_flag = DA9052_STROBE_MANUAL; + + return 0; +} + +static int da9052_sm_hw_deinit(struct da9052_wdt *wdt) +{ + struct da9052_ssc_msg msg; + int ret; + + if (monitoring_timer != NULL) + del_timer(monitoring_timer); + kfree(monitoring_timer); + + msg.addr = DA9052_CONTROLD_REG; + msg.data = 0; + + da9052_lock(wdt->da9052); + ret = wdt->da9052->read(wdt->da9052, &msg); + if (ret) + goto ssc_err; + da9052_unlock(wdt->da9052); + + msg.data = (msg.data & ~(DA9052_CONTROLD_TWDSCALE)); + da9052_lock(wdt->da9052); + ret = wdt->da9052->write(wdt->da9052, &msg); + if (ret) + goto ssc_err; + da9052_unlock(wdt->da9052); + + return 0; +ssc_err: + da9052_unlock(wdt->da9052); + return -EIO; +} + + s32 da9052_sm_set_strobing_filter(struct da9052_wdt *wdt, + u8 strobing_filter_state) + { + struct da9052_ssc_msg msg; + int ret = 0; + + msg.addr = DA9052_CONTROLD_REG; + msg.data = 0; + da9052_lock(wdt->da9052); + ret = wdt->da9052->read(wdt->da9052, &msg); + if (ret) + goto ssc_err; + da9052_unlock(wdt->da9052); + + msg.data = (msg.data & DA9052_CONTROLD_TWDSCALE); + + if (strobing_filter_state == ENABLE) { + sm_strobe_filter_flag = ENABLE; + if (DA9052_WDT_DISABLE == msg.data) { + sm_str_req = DISABLE; + del_timer(monitoring_timer); + return 0; + } + if (DA9052_SCALE_64X == msg.data) + sm_mon_interval = msecs_to_jiffies(DA9052_X64_WINDOW); + else if (DA9052_SCALE_32X == msg.data) + sm_mon_interval = msecs_to_jiffies(DA9052_X32_WINDOW); + else if (DA9052_SCALE_16X == msg.data) + sm_mon_interval = msecs_to_jiffies(DA9052_X16_WINDOW); + else if (DA9052_SCALE_8X == msg.data) + sm_mon_interval = msecs_to_jiffies(DA9052_X8_WINDOW); + else if (DA9052_SCALE_4X == msg.data) + sm_mon_interval = msecs_to_jiffies(DA9052_X4_WINDOW); + else if (DA9052_SCALE_2X == msg.data) + sm_mon_interval = msecs_to_jiffies(DA9052_X2_WINDOW); + else + sm_mon_interval = msecs_to_jiffies(DA9052_X1_WINDOW); + + } else if (strobing_filter_state == DISABLE) { + sm_strobe_filter_flag = DISABLE; + sm_mon_interval = msecs_to_jiffies(DA9052_ADC_TWDMIN_TIME); + if (DA9052_WDT_DISABLE == msg.data) { + sm_str_req = DISABLE; + del_timer(monitoring_timer); + return 0; + } + } else { + return STROBING_FILTER_ERROR; + } + mod_timer(monitoring_timer, jiffies + sm_mon_interval); + + return 0; +ssc_err: + da9052_unlock(wdt->da9052); + return -EIO; +} + +int da9052_sm_set_strobing_mode(u8 strobing_mode_state) +{ + if (strobing_mode_state == DA9052_STROBE_AUTO) + sm_strobe_mode_flag = DA9052_STROBE_AUTO; + else if (strobing_mode_state == DA9052_STROBE_MANUAL) + sm_strobe_mode_flag = DA9052_STROBE_MANUAL; + else + return STROBING_MODE_ERROR; + + return 0; +} + +int da9052_sm_strobe_wdt(void) +{ + sm_str_req = ENABLE; + return 0; +} + + s32 da9052_sm_set_wdt(struct da9052_wdt *wdt, u8 wdt_scaling) +{ + struct da9052_ssc_msg msg; + int ret = 0; + + + if (wdt_scaling > DA9052_SCALE_64X) + return INVALID_SCALING_VALUE; + + msg.addr = DA9052_CONTROLD_REG; + msg.data = 0; + da9052_lock(wdt->da9052); + ret = wdt->da9052->read(wdt->da9052, &msg); + if (ret) + goto ssc_err; + da9052_unlock(wdt->da9052); + + if (!((DA9052_WDT_DISABLE == (msg.data & DA9052_CONTROLD_TWDSCALE)) && + (DA9052_WDT_DISABLE == wdt_scaling))) { + msg.data = (msg.data & ~(DA9052_CONTROLD_TWDSCALE)); + msg.addr = DA9052_CONTROLD_REG; + + + da9052_lock(wdt->da9052); + ret = wdt->da9052->write(wdt->da9052, &msg); + if (ret) + goto ssc_err; + da9052_unlock(wdt->da9052); + + msleep(1); + da9052_lock(wdt->da9052); + ret = wdt->da9052->read(wdt->da9052, &msg); + if (ret) + goto ssc_err; + da9052_unlock(wdt->da9052); + + + msg.data |= wdt_scaling; + + da9052_lock(wdt->da9052); + ret = wdt->da9052->write(wdt->da9052, &msg); + if (ret) + goto ssc_err; + da9052_unlock(wdt->da9052); + + sm_str_req = DISABLE; + if (DA9052_WDT_DISABLE == wdt_scaling) { + del_timer(monitoring_timer); + return 0; + } + if (sm_strobe_filter_flag == ENABLE) { + if (DA9052_SCALE_64X == wdt_scaling) { + sm_mon_interval = + msecs_to_jiffies(DA9052_X64_WINDOW); + } else if (DA9052_SCALE_32X == wdt_scaling) { + sm_mon_interval = + msecs_to_jiffies(DA9052_X32_WINDOW); + } else if (DA9052_SCALE_16X == wdt_scaling) { + sm_mon_interval = + msecs_to_jiffies(DA9052_X16_WINDOW); + } else if (DA9052_SCALE_8X == wdt_scaling) { + sm_mon_interval = + msecs_to_jiffies(DA9052_X8_WINDOW); + } else if (DA9052_SCALE_4X == wdt_scaling) { + sm_mon_interval = + msecs_to_jiffies(DA9052_X4_WINDOW); + } else if (DA9052_SCALE_2X == wdt_scaling) { + sm_mon_interval = + msecs_to_jiffies(DA9052_X2_WINDOW); + } else { + sm_mon_interval = + msecs_to_jiffies(DA9052_X1_WINDOW); + } + } else { + sm_mon_interval = msecs_to_jiffies( + DA9052_ADC_TWDMIN_TIME); + } + mod_timer(monitoring_timer, jiffies + sm_mon_interval); + } + + return 0; +ssc_err: + da9052_unlock(wdt->da9052); + return -EIO; +} + +static int da9052_wdt_open(struct inode *inode, struct file *file) +{ + struct da9052_wdt *wdt = get_wdt_da9052(); + int ret; + printk(KERN_INFO"IN WDT OPEN \n"); + + if (!wdt) { + printk(KERN_INFO"Returning no device\n"); + return -ENODEV; + } + printk(KERN_INFO"IN WDT OPEN 1\n"); + + if (test_and_set_bit(0, &da9052_wdt_users)) + return -EBUSY; + + ret = da9052_sm_hw_init(wdt); + if (ret != 0) { + printk(KERN_ERR "Watchdog hw init failed\n"); + return ret; + } + + return nonseekable_open(inode, file); +} + +static int da9052_wdt_release(struct inode *inode, struct file *file) +{ + struct da9052_wdt *wdt = get_wdt_da9052(); + + if (da9052_wdt_expect_close == 42) + da9052_sm_hw_deinit(wdt); + else + da9052_sm_strobe_wdt(); + da9052_wdt_expect_close = 0; + clear_bit(0, &da9052_wdt_users); + return 0; +} + +static ssize_t da9052_wdt_write(struct file *file, + const char __user *data, size_t count, + loff_t *ppos) +{ + size_t i; + + if (count) { + if (!nowayout) { + /* In case it was set long ago */ + da9052_wdt_expect_close = 0; + for (i = 0; i != count; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + da9052_wdt_expect_close = 42; + } + } + da9052_sm_strobe_wdt(); + } + return count; +} + +static struct watchdog_info da9052_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "DA9052_SM Watchdog", +}; + +static long da9052_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct da9052_wdt *wdt = get_wdt_da9052(); + void __user *argp = (void __user *)arg; + int __user *p = argp; + unsigned char new_value; + + switch (cmd) { + + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &da9052_wdt_info, + sizeof(da9052_wdt_info)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_SETOPTIONS: + if (get_user(new_value, p)) + return -EFAULT; + if (new_value & DA9052_STROBING_FILTER_ENABLE) + da9052_sm_set_strobing_filter(wdt, ENABLE); + if (new_value & DA9052_STROBING_FILTER_DISABLE) + da9052_sm_set_strobing_filter(wdt, DISABLE); + if (new_value & DA9052_SET_STROBING_MODE_MANUAL) + da9052_sm_set_strobing_mode(DA9052_STROBE_MANUAL); + if (new_value & DA9052_SET_STROBING_MODE_AUTO) + da9052_sm_set_strobing_mode(DA9052_STROBE_AUTO); + return 0; + case WDIOC_KEEPALIVE: + if (da9052_sm_strobe_wdt()) + return -EFAULT; + else + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + da9052_sm_scale = new_value; + if (da9052_sm_set_wdt(wdt, da9052_sm_scale)) + return -EFAULT; + case WDIOC_GETTIMEOUT: + return put_user(sm_mon_interval, p); + default: + return -ENOTTY; + } + return 0; +} + +static const struct file_operations da9052_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = da9052_wdt_ioctl, + .write = da9052_wdt_write, + .open = da9052_wdt_open, + .release = da9052_wdt_release, +}; + +static struct miscdevice da9052_wdt_miscdev = { + .minor = 255, + .name = "da9052-wdt", + .fops = &da9052_wdt_fops, +}; + +static int __devinit da9052_sm_probe(struct platform_device *pdev) +{ + int ret; + struct da9052_wdt *wdt; + struct da9052_ssc_msg msg; + + wdt = kzalloc(sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + wdt->da9052 = dev_get_drvdata(pdev->dev.parent); + platform_set_drvdata(pdev, wdt); + + msg.addr = DA9052_CONTROLD_REG; + msg.data = 0; + + da9052_lock(wdt->da9052); + ret = wdt->da9052->read(wdt->da9052, &msg); + if (ret) { + da9052_unlock(wdt->da9052); + goto err_ssc_comm; + } + printk(KERN_INFO"DA9052 SM probe - 0 \n"); + + msg.data = (msg.data & ~(DA9052_CONTROLD_TWDSCALE)); + ret = wdt->da9052->write(wdt->da9052, &msg); + if (ret) { + da9052_unlock(wdt->da9052); + goto err_ssc_comm; + } + da9052_unlock(wdt->da9052); + + da9052_wdt_miscdev.parent = &pdev->dev; + + ret = misc_register(&da9052_wdt_miscdev); + if (ret != 0) { + platform_set_drvdata(pdev, NULL); + kfree(wdt); + return -EFAULT; + } + return 0; +err_ssc_comm: + platform_set_drvdata(pdev, NULL); + kfree(wdt); + return -EIO; +} + +static int __devexit da9052_sm_remove(struct platform_device *dev) +{ + misc_deregister(&da9052_wdt_miscdev); + + return 0; +} + +static struct platform_driver da9052_sm_driver = { + .probe = da9052_sm_probe, + .remove = __devexit_p(da9052_sm_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init da9052_sm_init(void) +{ + return platform_driver_register(&da9052_sm_driver); +} +module_init(da9052_sm_init); + +static void __exit da9052_sm_exit(void) +{ + platform_driver_unregister(&da9052_sm_driver); +} +module_exit(da9052_sm_exit); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>") +MODULE_DESCRIPTION("DA9052 SM Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); |