From bfed345edfe05b291f7e5d396d4b447b6e8e66fa Mon Sep 17 00:00:00 2001 From: Zhangfei Gao Date: Mon, 20 Jun 2011 22:11:52 +0800 Subject: mmc: sdhci-pxa: move platform data to include/linux/platform_data As suggested by Arnd, move platform data to include/linux/platform_data in order to improve build coverage for the driver. Signed-off-by: Zhangfei Gao Acked-by: Arnd Bergmann Signed-off-by: Chris Ball --- include/linux/platform_data/pxa_sdhci.h | 60 +++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 include/linux/platform_data/pxa_sdhci.h (limited to 'include/linux/platform_data') diff --git a/include/linux/platform_data/pxa_sdhci.h b/include/linux/platform_data/pxa_sdhci.h new file mode 100644 index 000000000000..51ad0995abac --- /dev/null +++ b/include/linux/platform_data/pxa_sdhci.h @@ -0,0 +1,60 @@ +/* + * include/linux/platform_data/pxa_sdhci.h + * + * Copyright 2010 Marvell + * Zhangfei Gao + * + * PXA Platform - SDHCI platform data definitions + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _PXA_SDHCI_H_ +#define _PXA_SDHCI_H_ + +/* pxa specific flag */ +/* Require clock free running */ +#define PXA_FLAG_ENABLE_CLOCK_GATING (1<<0) +/* card always wired to host, like on-chip emmc */ +#define PXA_FLAG_CARD_PERMANENT (1<<1) +/* Board design supports 8-bit data on SD/SDIO BUS */ +#define PXA_FLAG_SD_8_BIT_CAPABLE_SLOT (1<<2) + +/* + * struct pxa_sdhci_platdata() - Platform device data for PXA SDHCI + * @flags: flags for platform requirement + * @clk_delay_cycles: + * mmp2: each step is roughly 100ps, 5bits width + * pxa910: each step is 1ns, 4bits width + * @clk_delay_sel: select clk_delay, used on pxa910 + * 0: choose feedback clk + * 1: choose feedback clk + delay value + * 2: choose internal clk + * @clk_delay_enable: enable clk_delay or not, used on pxa910 + * @ext_cd_gpio: gpio pin used for external CD line + * @ext_cd_gpio_invert: invert values for external CD gpio line + * @max_speed: the maximum speed supported + * @host_caps: Standard MMC host capabilities bit field. + * @quirks: quirks of platfrom + * @pm_caps: pm_caps of platfrom + */ +struct sdhci_pxa_platdata { + unsigned int flags; + unsigned int clk_delay_cycles; + unsigned int clk_delay_sel; + bool clk_delay_enable; + unsigned int ext_cd_gpio; + bool ext_cd_gpio_invert; + unsigned int max_speed; + unsigned int host_caps; + unsigned int quirks; + unsigned int pm_caps; +}; + +struct sdhci_pxa { + u8 clk_enable; + u8 power_mode; +}; +#endif /* _PXA_SDHCI_H_ */ -- cgit v1.2.3 From a1bb73d76bc814e9385390e6aa9880d884322e2e Mon Sep 17 00:00:00 2001 From: Donggeun Kim Date: Mon, 25 Jul 2011 17:13:07 -0700 Subject: drivers/misc: add support the FSA9480 USB Switch The FSA9480 is a USB port accessory detector and switch. This patch adds support the FSA9480 USB Switch. [akpm@linux-foundation.org: make a couple of things static] Signed-off-by: Donggeun Kim Signed-off-by: Minkyu Kang Signed-off-by: Kyungmin Park Cc: Greg KH Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- .../ABI/testing/sysfs-bus-i2c-devices-fsa9480 | 21 + drivers/misc/Kconfig | 9 + drivers/misc/Makefile | 1 + drivers/misc/fsa9480.c | 557 +++++++++++++++++++++ include/linux/platform_data/fsa9480.h | 27 + 5 files changed, 615 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-fsa9480 create mode 100644 drivers/misc/fsa9480.c create mode 100644 include/linux/platform_data/fsa9480.h (limited to 'include/linux/platform_data') diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-fsa9480 b/Documentation/ABI/testing/sysfs-bus-i2c-devices-fsa9480 new file mode 100644 index 000000000000..9de269bb0ae5 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-fsa9480 @@ -0,0 +1,21 @@ +What: /sys/bus/i2c/devices/.../device +Date: February 2011 +Contact: Minkyu Kang +Description: + show what device is attached + NONE - no device + USB - USB device is attached + UART - UART is attached + CHARGER - Charger is attaced + JIG - JIG is attached + +What: /sys/bus/i2c/devices/.../switch +Date: February 2011 +Contact: Minkyu Kang +Description: + show or set the state of manual switch + VAUDIO - switch to VAUDIO path + UART - switch to UART path + AUDIO - switch to AUDIO path + DHOST - switch to DHOST path + AUTO - switch automatically by device diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 3546474428f8..0a4d86c6c4a4 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -489,6 +489,15 @@ config PCH_PHUB To compile this driver as a module, choose M here: the module will be called pch_phub. +config USB_SWITCH_FSA9480 + tristate "FSA9480 USB Switch" + depends on I2C + help + The FSA9480 is a USB port accessory detector and switch. + The FSA9480 is fully controlled using I2C and enables USB data, + stereo and mono audio, video, microphone and UART data to use + a common connector port. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 5f03172cc0b5..33282157bc3c 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -46,3 +46,4 @@ obj-y += ti-st/ obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o obj-y += lis3lv02d/ obj-y += carma/ +obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o diff --git a/drivers/misc/fsa9480.c b/drivers/misc/fsa9480.c new file mode 100644 index 000000000000..5325a7e70dcf --- /dev/null +++ b/drivers/misc/fsa9480.c @@ -0,0 +1,557 @@ +/* + * fsa9480.c - FSA9480 micro USB switch device driver + * + * Copyright (C) 2010 Samsung Electronics + * Minkyu Kang + * Wonguk Jeong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* FSA9480 I2C registers */ +#define FSA9480_REG_DEVID 0x01 +#define FSA9480_REG_CTRL 0x02 +#define FSA9480_REG_INT1 0x03 +#define FSA9480_REG_INT2 0x04 +#define FSA9480_REG_INT1_MASK 0x05 +#define FSA9480_REG_INT2_MASK 0x06 +#define FSA9480_REG_ADC 0x07 +#define FSA9480_REG_TIMING1 0x08 +#define FSA9480_REG_TIMING2 0x09 +#define FSA9480_REG_DEV_T1 0x0a +#define FSA9480_REG_DEV_T2 0x0b +#define FSA9480_REG_BTN1 0x0c +#define FSA9480_REG_BTN2 0x0d +#define FSA9480_REG_CK 0x0e +#define FSA9480_REG_CK_INT1 0x0f +#define FSA9480_REG_CK_INT2 0x10 +#define FSA9480_REG_CK_INTMASK1 0x11 +#define FSA9480_REG_CK_INTMASK2 0x12 +#define FSA9480_REG_MANSW1 0x13 +#define FSA9480_REG_MANSW2 0x14 + +/* Control */ +#define CON_SWITCH_OPEN (1 << 4) +#define CON_RAW_DATA (1 << 3) +#define CON_MANUAL_SW (1 << 2) +#define CON_WAIT (1 << 1) +#define CON_INT_MASK (1 << 0) +#define CON_MASK (CON_SWITCH_OPEN | CON_RAW_DATA | \ + CON_MANUAL_SW | CON_WAIT) + +/* Device Type 1 */ +#define DEV_USB_OTG (1 << 7) +#define DEV_DEDICATED_CHG (1 << 6) +#define DEV_USB_CHG (1 << 5) +#define DEV_CAR_KIT (1 << 4) +#define DEV_UART (1 << 3) +#define DEV_USB (1 << 2) +#define DEV_AUDIO_2 (1 << 1) +#define DEV_AUDIO_1 (1 << 0) + +#define DEV_T1_USB_MASK (DEV_USB_OTG | DEV_USB) +#define DEV_T1_UART_MASK (DEV_UART) +#define DEV_T1_CHARGER_MASK (DEV_DEDICATED_CHG | DEV_USB_CHG) + +/* Device Type 2 */ +#define DEV_AV (1 << 6) +#define DEV_TTY (1 << 5) +#define DEV_PPD (1 << 4) +#define DEV_JIG_UART_OFF (1 << 3) +#define DEV_JIG_UART_ON (1 << 2) +#define DEV_JIG_USB_OFF (1 << 1) +#define DEV_JIG_USB_ON (1 << 0) + +#define DEV_T2_USB_MASK (DEV_JIG_USB_OFF | DEV_JIG_USB_ON) +#define DEV_T2_UART_MASK (DEV_JIG_UART_OFF | DEV_JIG_UART_ON) +#define DEV_T2_JIG_MASK (DEV_JIG_USB_OFF | DEV_JIG_USB_ON | \ + DEV_JIG_UART_OFF | DEV_JIG_UART_ON) + +/* + * Manual Switch + * D- [7:5] / D+ [4:2] + * 000: Open all / 001: USB / 010: AUDIO / 011: UART / 100: V_AUDIO + */ +#define SW_VAUDIO ((4 << 5) | (4 << 2)) +#define SW_UART ((3 << 5) | (3 << 2)) +#define SW_AUDIO ((2 << 5) | (2 << 2)) +#define SW_DHOST ((1 << 5) | (1 << 2)) +#define SW_AUTO ((0 << 5) | (0 << 2)) + +/* Interrupt 1 */ +#define INT_DETACH (1 << 1) +#define INT_ATTACH (1 << 0) + +struct fsa9480_usbsw { + struct i2c_client *client; + struct fsa9480_platform_data *pdata; + int dev1; + int dev2; + int mansw; +}; + +static struct fsa9480_usbsw *chip; + +static int fsa9480_write_reg(struct i2c_client *client, + int reg, int value) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, value); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static int fsa9480_read_reg(struct i2c_client *client, int reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static int fsa9480_read_irq(struct i2c_client *client, int *value) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, + FSA9480_REG_INT1, 2, (u8 *)value); + *value &= 0xffff; + + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static void fsa9480_set_switch(const char *buf) +{ + struct fsa9480_usbsw *usbsw = chip; + struct i2c_client *client = usbsw->client; + unsigned int value; + unsigned int path = 0; + + value = fsa9480_read_reg(client, FSA9480_REG_CTRL); + + if (!strncmp(buf, "VAUDIO", 6)) { + path = SW_VAUDIO; + value &= ~CON_MANUAL_SW; + } else if (!strncmp(buf, "UART", 4)) { + path = SW_UART; + value &= ~CON_MANUAL_SW; + } else if (!strncmp(buf, "AUDIO", 5)) { + path = SW_AUDIO; + value &= ~CON_MANUAL_SW; + } else if (!strncmp(buf, "DHOST", 5)) { + path = SW_DHOST; + value &= ~CON_MANUAL_SW; + } else if (!strncmp(buf, "AUTO", 4)) { + path = SW_AUTO; + value |= CON_MANUAL_SW; + } else { + printk(KERN_ERR "Wrong command\n"); + return; + } + + usbsw->mansw = path; + fsa9480_write_reg(client, FSA9480_REG_MANSW1, path); + fsa9480_write_reg(client, FSA9480_REG_CTRL, value); +} + +static ssize_t fsa9480_get_switch(char *buf) +{ + struct fsa9480_usbsw *usbsw = chip; + struct i2c_client *client = usbsw->client; + unsigned int value; + + value = fsa9480_read_reg(client, FSA9480_REG_MANSW1); + + if (value == SW_VAUDIO) + return sprintf(buf, "VAUDIO\n"); + else if (value == SW_UART) + return sprintf(buf, "UART\n"); + else if (value == SW_AUDIO) + return sprintf(buf, "AUDIO\n"); + else if (value == SW_DHOST) + return sprintf(buf, "DHOST\n"); + else if (value == SW_AUTO) + return sprintf(buf, "AUTO\n"); + else + return sprintf(buf, "%x", value); +} + +static ssize_t fsa9480_show_device(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fsa9480_usbsw *usbsw = dev_get_drvdata(dev); + struct i2c_client *client = usbsw->client; + int dev1, dev2; + + dev1 = fsa9480_read_reg(client, FSA9480_REG_DEV_T1); + dev2 = fsa9480_read_reg(client, FSA9480_REG_DEV_T2); + + if (!dev1 && !dev2) + return sprintf(buf, "NONE\n"); + + /* USB */ + if (dev1 & DEV_T1_USB_MASK || dev2 & DEV_T2_USB_MASK) + return sprintf(buf, "USB\n"); + + /* UART */ + if (dev1 & DEV_T1_UART_MASK || dev2 & DEV_T2_UART_MASK) + return sprintf(buf, "UART\n"); + + /* CHARGER */ + if (dev1 & DEV_T1_CHARGER_MASK) + return sprintf(buf, "CHARGER\n"); + + /* JIG */ + if (dev2 & DEV_T2_JIG_MASK) + return sprintf(buf, "JIG\n"); + + return sprintf(buf, "UNKNOWN\n"); +} + +static ssize_t fsa9480_show_manualsw(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return fsa9480_get_switch(buf); + +} + +static ssize_t fsa9480_set_manualsw(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + fsa9480_set_switch(buf); + + return count; +} + +static DEVICE_ATTR(device, S_IRUGO, fsa9480_show_device, NULL); +static DEVICE_ATTR(switch, S_IRUGO | S_IWUSR, + fsa9480_show_manualsw, fsa9480_set_manualsw); + +static struct attribute *fsa9480_attributes[] = { + &dev_attr_device.attr, + &dev_attr_switch.attr, + NULL +}; + +static const struct attribute_group fsa9480_group = { + .attrs = fsa9480_attributes, +}; + +static void fsa9480_detect_dev(struct fsa9480_usbsw *usbsw, int intr) +{ + int val1, val2, ctrl; + struct fsa9480_platform_data *pdata = usbsw->pdata; + struct i2c_client *client = usbsw->client; + + val1 = fsa9480_read_reg(client, FSA9480_REG_DEV_T1); + val2 = fsa9480_read_reg(client, FSA9480_REG_DEV_T2); + ctrl = fsa9480_read_reg(client, FSA9480_REG_CTRL); + + dev_info(&client->dev, "intr: 0x%x, dev1: 0x%x, dev2: 0x%x\n", + intr, val1, val2); + + if (!intr) + goto out; + + if (intr & INT_ATTACH) { /* Attached */ + /* USB */ + if (val1 & DEV_T1_USB_MASK || val2 & DEV_T2_USB_MASK) { + if (pdata->usb_cb) + pdata->usb_cb(FSA9480_ATTACHED); + + if (usbsw->mansw) { + fsa9480_write_reg(client, + FSA9480_REG_MANSW1, usbsw->mansw); + } + } + + /* UART */ + if (val1 & DEV_T1_UART_MASK || val2 & DEV_T2_UART_MASK) { + if (pdata->uart_cb) + pdata->uart_cb(FSA9480_ATTACHED); + + if (!(ctrl & CON_MANUAL_SW)) { + fsa9480_write_reg(client, + FSA9480_REG_MANSW1, SW_UART); + } + } + + /* CHARGER */ + if (val1 & DEV_T1_CHARGER_MASK) { + if (pdata->charger_cb) + pdata->charger_cb(FSA9480_ATTACHED); + } + + /* JIG */ + if (val2 & DEV_T2_JIG_MASK) { + if (pdata->jig_cb) + pdata->jig_cb(FSA9480_ATTACHED); + } + } else if (intr & INT_DETACH) { /* Detached */ + /* USB */ + if (usbsw->dev1 & DEV_T1_USB_MASK || + usbsw->dev2 & DEV_T2_USB_MASK) { + if (pdata->usb_cb) + pdata->usb_cb(FSA9480_DETACHED); + } + + /* UART */ + if (usbsw->dev1 & DEV_T1_UART_MASK || + usbsw->dev2 & DEV_T2_UART_MASK) { + if (pdata->uart_cb) + pdata->uart_cb(FSA9480_DETACHED); + } + + /* CHARGER */ + if (usbsw->dev1 & DEV_T1_CHARGER_MASK) { + if (pdata->charger_cb) + pdata->charger_cb(FSA9480_DETACHED); + } + + /* JIG */ + if (usbsw->dev2 & DEV_T2_JIG_MASK) { + if (pdata->jig_cb) + pdata->jig_cb(FSA9480_DETACHED); + } + } + + usbsw->dev1 = val1; + usbsw->dev2 = val2; + +out: + ctrl &= ~CON_INT_MASK; + fsa9480_write_reg(client, FSA9480_REG_CTRL, ctrl); +} + +static irqreturn_t fsa9480_irq_handler(int irq, void *data) +{ + struct fsa9480_usbsw *usbsw = data; + struct i2c_client *client = usbsw->client; + int intr; + + /* clear interrupt */ + fsa9480_read_irq(client, &intr); + + /* device detection */ + fsa9480_detect_dev(usbsw, intr); + + return IRQ_HANDLED; +} + +static int fsa9480_irq_init(struct fsa9480_usbsw *usbsw) +{ + struct fsa9480_platform_data *pdata = usbsw->pdata; + struct i2c_client *client = usbsw->client; + int ret; + int intr; + unsigned int ctrl = CON_MASK; + + /* clear interrupt */ + fsa9480_read_irq(client, &intr); + + /* unmask interrupt (attach/detach only) */ + fsa9480_write_reg(client, FSA9480_REG_INT1_MASK, 0xfc); + fsa9480_write_reg(client, FSA9480_REG_INT2_MASK, 0x1f); + + usbsw->mansw = fsa9480_read_reg(client, FSA9480_REG_MANSW1); + + if (usbsw->mansw) + ctrl &= ~CON_MANUAL_SW; /* Manual Switching Mode */ + + fsa9480_write_reg(client, FSA9480_REG_CTRL, ctrl); + + if (pdata && pdata->cfg_gpio) + pdata->cfg_gpio(); + + if (client->irq) { + ret = request_threaded_irq(client->irq, NULL, + fsa9480_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "fsa9480 micro USB", usbsw); + if (ret) { + dev_err(&client->dev, "failed to reqeust IRQ\n"); + return ret; + } + + device_init_wakeup(&client->dev, pdata->wakeup); + } + + return 0; +} + +static int __devinit fsa9480_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct fsa9480_usbsw *usbsw; + int ret = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + usbsw = kzalloc(sizeof(struct fsa9480_usbsw), GFP_KERNEL); + if (!usbsw) { + dev_err(&client->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + usbsw->client = client; + usbsw->pdata = client->dev.platform_data; + + chip = usbsw; + + i2c_set_clientdata(client, usbsw); + + ret = fsa9480_irq_init(usbsw); + if (ret) + goto fail1; + + ret = sysfs_create_group(&client->dev.kobj, &fsa9480_group); + if (ret) { + dev_err(&client->dev, + "failed to create fsa9480 attribute group\n"); + goto fail2; + } + + /* ADC Detect Time: 500ms */ + fsa9480_write_reg(client, FSA9480_REG_TIMING1, 0x6); + + if (chip->pdata->reset_cb) + chip->pdata->reset_cb(); + + /* device detection */ + fsa9480_detect_dev(usbsw, INT_ATTACH); + + pm_runtime_set_active(&client->dev); + + return 0; + +fail2: + if (client->irq) + free_irq(client->irq, NULL); +fail1: + i2c_set_clientdata(client, NULL); + kfree(usbsw); + return ret; +} + +static int __devexit fsa9480_remove(struct i2c_client *client) +{ + struct fsa9480_usbsw *usbsw = i2c_get_clientdata(client); + if (client->irq) + free_irq(client->irq, NULL); + i2c_set_clientdata(client, NULL); + + sysfs_remove_group(&client->dev.kobj, &fsa9480_group); + device_init_wakeup(&client->dev, 0); + kfree(usbsw); + return 0; +} + +#ifdef CONFIG_PM + +static int fsa9480_suspend(struct i2c_client *client, pm_message_t state) +{ + struct fsa9480_usbsw *usbsw = i2c_get_clientdata(client); + struct fsa9480_platform_data *pdata = usbsw->pdata; + + if (device_may_wakeup(&client->dev) && client->irq) + enable_irq_wake(client->irq); + + if (pdata->usb_power) + pdata->usb_power(0); + + return 0; +} + +static int fsa9480_resume(struct i2c_client *client) +{ + struct fsa9480_usbsw *usbsw = i2c_get_clientdata(client); + int dev1, dev2; + + if (device_may_wakeup(&client->dev) && client->irq) + disable_irq_wake(client->irq); + + /* + * Clear Pending interrupt. Note that detect_dev does what + * the interrupt handler does. So, we don't miss pending and + * we reenable interrupt if there is one. + */ + fsa9480_read_reg(client, FSA9480_REG_INT1); + fsa9480_read_reg(client, FSA9480_REG_INT2); + + dev1 = fsa9480_read_reg(client, FSA9480_REG_DEV_T1); + dev2 = fsa9480_read_reg(client, FSA9480_REG_DEV_T2); + + /* device detection */ + fsa9480_detect_dev(usbsw, (dev1 || dev2) ? INT_ATTACH : INT_DETACH); + + return 0; +} + +#else + +#define fsa9480_suspend NULL +#define fsa9480_resume NULL + +#endif /* CONFIG_PM */ + +static const struct i2c_device_id fsa9480_id[] = { + {"fsa9480", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, fsa9480_id); + +static struct i2c_driver fsa9480_i2c_driver = { + .driver = { + .name = "fsa9480", + }, + .probe = fsa9480_probe, + .remove = __devexit_p(fsa9480_remove), + .resume = fsa9480_resume, + .suspend = fsa9480_suspend, + .id_table = fsa9480_id, +}; + +static int __init fsa9480_init(void) +{ + return i2c_add_driver(&fsa9480_i2c_driver); +} +module_init(fsa9480_init); + +static void __exit fsa9480_exit(void) +{ + i2c_del_driver(&fsa9480_i2c_driver); +} +module_exit(fsa9480_exit); + +MODULE_AUTHOR("Minkyu Kang "); +MODULE_DESCRIPTION("FSA9480 USB Switch driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/fsa9480.h b/include/linux/platform_data/fsa9480.h new file mode 100644 index 000000000000..72dddcb4bed1 --- /dev/null +++ b/include/linux/platform_data/fsa9480.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2010 Samsung Electronics + * Minkyu Kang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _FSA9480_H_ +#define _FSA9480_H_ + +#define FSA9480_ATTACHED 1 +#define FSA9480_DETACHED 0 + +struct fsa9480_platform_data { + void (*cfg_gpio) (void); + void (*usb_cb) (u8 attached); + void (*uart_cb) (u8 attached); + void (*charger_cb) (u8 attached); + void (*jig_cb) (u8 attached); + void (*reset_cb) (void); + void (*usb_power) (u8 on); + int wakeup; +}; + +#endif /* _FSA9480_H_ */ -- cgit v1.2.3 From f22aaaa70d8c24e5dc7d23a219c4beace8354b65 Mon Sep 17 00:00:00 2001 From: Donggeun Kim Date: Mon, 20 Jun 2011 16:48:19 +0900 Subject: hwmon: Driver for NTC Thermistors Add support for NTC Thermistor series. In this release, the following thermistors are supported: NCP15WB473, NCP18WB473, NCP03WB473, and NCP15WL333. This driver is based on the datasheet of MURATA. The driver in the patch does conversion from the raw ADC value (either voltage or resistence) to temperature. In order to use voltage values as input, the circuit schematics should be provided with the platform data. A compensation table for each type of thermistor is provided for the conversion. Signed-off-by: Donggeun Kim Signed-off-by: MyungJoo Ham Signed-off-by: KyungMin Park Reviewed-by: Shubhrajyoti D Signed-off-by: Guenter Roeck --- Documentation/hwmon/ntc_thermistor | 93 ++++++ drivers/hwmon/Kconfig | 14 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ntc_thermistor.c | 453 +++++++++++++++++++++++++++ include/linux/platform_data/ntc_thermistor.h | 53 ++++ 5 files changed, 614 insertions(+) create mode 100644 Documentation/hwmon/ntc_thermistor create mode 100644 drivers/hwmon/ntc_thermistor.c create mode 100644 include/linux/platform_data/ntc_thermistor.h (limited to 'include/linux/platform_data') diff --git a/Documentation/hwmon/ntc_thermistor b/Documentation/hwmon/ntc_thermistor new file mode 100644 index 000000000000..3bfda94096fd --- /dev/null +++ b/Documentation/hwmon/ntc_thermistor @@ -0,0 +1,93 @@ +Kernel driver ntc_thermistor +================= + +Supported thermistors: +* Murata NTC Thermistors NCP15WB473, NCP18WB473, NCP21WB473, NCP03WB473, NCP15WL333 + Prefixes: 'ncp15wb473', 'ncp18wb473', 'ncp21wb473', 'ncp03wb473', 'ncp15wl333' + Datasheet: Publicly available at Murata + +Other NTC thermistors can be supported simply by adding compensation +tables; e.g., NCP15WL333 support is added by the table ncpXXwl333. + +Authors: + MyungJoo Ham + +Description +----------- + +The NTC thermistor is a simple thermistor that requires users to provide the +resistance and lookup the corresponding compensation table to get the +temperature input. + +The NTC driver provides lookup tables with a linear approximation function +and four circuit models with an option not to use any of the four models. + +The four circuit models provided are: + + $: resister, [TH]: the thermistor + + 1. connect = NTC_CONNECTED_POSITIVE, pullup_ohm > 0 + + [pullup_uV] + | | + [TH] $ (pullup_ohm) + | | + +----+-----------------------[read_uV] + | + $ (pulldown_ohm) + | + --- (ground) + + 2. connect = NTC_CONNECTED_POSITIVE, pullup_ohm = 0 (not-connected) + + [pullup_uV] + | + [TH] + | + +----------------------------[read_uV] + | + $ (pulldown_ohm) + | + --- (ground) + + 3. connect = NTC_CONNECTED_GROUND, pulldown_ohm > 0 + + [pullup_uV] + | + $ (pullup_ohm) + | + +----+-----------------------[read_uV] + | | + [TH] $ (pulldown_ohm) + | | + -------- (ground) + + 4. connect = NTC_CONNECTED_GROUND, pulldown_ohm = 0 (not-connected) + + [pullup_uV] + | + $ (pullup_ohm) + | + +----------------------------[read_uV] + | + [TH] + | + --- (ground) + +When one of the four circuit models is used, read_uV, pullup_uV, pullup_ohm, +pulldown_ohm, and connect should be provided. When none of the four models +are suitable or the user can get the resistance directly, the user should +provide read_ohm and _not_ provide the others. + +Sysfs Interface +--------------- +name the mandatory global attribute, the thermistor name. + +temp1_type always 4 (thermistor) + RO + +temp1_input measure the temperature and provide the measured value. + (reading this file initiates the reading procedure.) + RO + +Note that each NTC thermistor has only _one_ thermistor; thus, only temp1 exists. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index dec5eee3542f..770fc8195627 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -777,6 +777,20 @@ config SENSORS_MAX6650 This driver can also be built as a module. If so, the module will be called max6650. +config SENSORS_NTC_THERMISTOR + tristate "NTC thermistor support" + depends on EXPERIMENTAL + help + This driver supports NTC thermistors sensor reading and its + interpretation. The driver can also monitor the temperature and + send notifications about the temperature. + + Currently, this driver supports + NCP15WB473, NCP18WB473, NCP21WB473, NCP03WB473, and NCP15WL333. + + This driver can also be built as a module. If so, the module + will be called ntc-thermistor. + config SENSORS_PC87360 tristate "National Semiconductor PC87360 family" select HWMON_VID diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index a6d519e568c0..1b7dc770aa60 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -92,6 +92,7 @@ obj-$(CONFIG_SENSORS_MAX6639) += max6639.o obj-$(CONFIG_SENSORS_MAX6642) += max6642.o obj-$(CONFIG_SENSORS_MAX6650) += max6650.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o +obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c new file mode 100644 index 000000000000..d7926f4336b5 --- /dev/null +++ b/drivers/hwmon/ntc_thermistor.c @@ -0,0 +1,453 @@ +/* + * ntc_thermistor.c - NTC Thermistors + * + * Copyright (C) 2010 Samsung Electronics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +struct ntc_compensation { + int temp_C; + unsigned int ohm; +}; + +/* + * A compensation table should be sorted by the values of .ohm + * in descending order. + * The following compensation tables are from the specification of Murata NTC + * Thermistors Datasheet + */ +const struct ntc_compensation ncpXXwb473[] = { + { .temp_C = -40, .ohm = 1747920 }, + { .temp_C = -35, .ohm = 1245428 }, + { .temp_C = -30, .ohm = 898485 }, + { .temp_C = -25, .ohm = 655802 }, + { .temp_C = -20, .ohm = 483954 }, + { .temp_C = -15, .ohm = 360850 }, + { .temp_C = -10, .ohm = 271697 }, + { .temp_C = -5, .ohm = 206463 }, + { .temp_C = 0, .ohm = 158214 }, + { .temp_C = 5, .ohm = 122259 }, + { .temp_C = 10, .ohm = 95227 }, + { .temp_C = 15, .ohm = 74730 }, + { .temp_C = 20, .ohm = 59065 }, + { .temp_C = 25, .ohm = 47000 }, + { .temp_C = 30, .ohm = 37643 }, + { .temp_C = 35, .ohm = 30334 }, + { .temp_C = 40, .ohm = 24591 }, + { .temp_C = 45, .ohm = 20048 }, + { .temp_C = 50, .ohm = 16433 }, + { .temp_C = 55, .ohm = 13539 }, + { .temp_C = 60, .ohm = 11209 }, + { .temp_C = 65, .ohm = 9328 }, + { .temp_C = 70, .ohm = 7798 }, + { .temp_C = 75, .ohm = 6544 }, + { .temp_C = 80, .ohm = 5518 }, + { .temp_C = 85, .ohm = 4674 }, + { .temp_C = 90, .ohm = 3972 }, + { .temp_C = 95, .ohm = 3388 }, + { .temp_C = 100, .ohm = 2902 }, + { .temp_C = 105, .ohm = 2494 }, + { .temp_C = 110, .ohm = 2150 }, + { .temp_C = 115, .ohm = 1860 }, + { .temp_C = 120, .ohm = 1615 }, + { .temp_C = 125, .ohm = 1406 }, +}; +const struct ntc_compensation ncpXXwl333[] = { + { .temp_C = -40, .ohm = 1610154 }, + { .temp_C = -35, .ohm = 1130850 }, + { .temp_C = -30, .ohm = 802609 }, + { .temp_C = -25, .ohm = 575385 }, + { .temp_C = -20, .ohm = 416464 }, + { .temp_C = -15, .ohm = 304219 }, + { .temp_C = -10, .ohm = 224193 }, + { .temp_C = -5, .ohm = 166623 }, + { .temp_C = 0, .ohm = 124850 }, + { .temp_C = 5, .ohm = 94287 }, + { .temp_C = 10, .ohm = 71747 }, + { .temp_C = 15, .ohm = 54996 }, + { .temp_C = 20, .ohm = 42455 }, + { .temp_C = 25, .ohm = 33000 }, + { .temp_C = 30, .ohm = 25822 }, + { .temp_C = 35, .ohm = 20335 }, + { .temp_C = 40, .ohm = 16115 }, + { .temp_C = 45, .ohm = 12849 }, + { .temp_C = 50, .ohm = 10306 }, + { .temp_C = 55, .ohm = 8314 }, + { .temp_C = 60, .ohm = 6746 }, + { .temp_C = 65, .ohm = 5503 }, + { .temp_C = 70, .ohm = 4513 }, + { .temp_C = 75, .ohm = 3721 }, + { .temp_C = 80, .ohm = 3084 }, + { .temp_C = 85, .ohm = 2569 }, + { .temp_C = 90, .ohm = 2151 }, + { .temp_C = 95, .ohm = 1809 }, + { .temp_C = 100, .ohm = 1529 }, + { .temp_C = 105, .ohm = 1299 }, + { .temp_C = 110, .ohm = 1108 }, + { .temp_C = 115, .ohm = 949 }, + { .temp_C = 120, .ohm = 817 }, + { .temp_C = 125, .ohm = 707 }, +}; + +struct ntc_data { + struct device *hwmon_dev; + struct ntc_thermistor_platform_data *pdata; + const struct ntc_compensation *comp; + struct device *dev; + int n_comp; + char name[PLATFORM_NAME_SIZE]; +}; + +static inline u64 div64_u64_safe(u64 dividend, u64 divisor) +{ + if (divisor == 0 && dividend == 0) + return 0; + if (divisor == 0) + return UINT_MAX; + return div64_u64(dividend, divisor); +} + +static unsigned int get_ohm_of_thermistor(struct ntc_data *data, + unsigned int uV) +{ + struct ntc_thermistor_platform_data *pdata = data->pdata; + u64 mV = uV / 1000; + u64 pmV = pdata->pullup_uV / 1000; + u64 N, puO, pdO; + puO = pdata->pullup_ohm; + pdO = pdata->pulldown_ohm; + + if (mV == 0) { + if (pdata->connect == NTC_CONNECTED_POSITIVE) + return UINT_MAX; + return 0; + } + if (mV >= pmV) + return (pdata->connect == NTC_CONNECTED_POSITIVE) ? + 0 : UINT_MAX; + + if (pdata->connect == NTC_CONNECTED_POSITIVE && puO == 0) + N = div64_u64_safe(pdO * (pmV - mV), mV); + else if (pdata->connect == NTC_CONNECTED_GROUND && pdO == 0) + N = div64_u64_safe(puO * mV, pmV - mV); + else if (pdata->connect == NTC_CONNECTED_POSITIVE) + N = div64_u64_safe(pdO * puO * (pmV - mV), + puO * mV - pdO * (pmV - mV)); + else + N = div64_u64_safe(pdO * puO * mV, pdO * (pmV - mV) - puO * mV); + + return (unsigned int) N; +} + +static int lookup_comp(struct ntc_data *data, + unsigned int ohm, int *i_low, int *i_high) +{ + int start, end, mid = -1; + + /* Do a binary search on compensation table */ + start = 0; + end = data->n_comp; + + while (end > start) { + mid = start + (end - start) / 2; + if (data->comp[mid].ohm < ohm) + end = mid; + else if (data->comp[mid].ohm > ohm) + start = mid + 1; + else + break; + } + + if (mid == 0) { + if (data->comp[mid].ohm > ohm) { + *i_high = mid; + *i_low = mid + 1; + return 0; + } else { + *i_low = mid; + *i_high = -1; + return -EINVAL; + } + } + if (mid == (data->n_comp - 1)) { + if (data->comp[mid].ohm <= ohm) { + *i_low = mid; + *i_high = mid - 1; + return 0; + } else { + *i_low = -1; + *i_high = mid; + return -EINVAL; + } + } + + if (data->comp[mid].ohm <= ohm) { + *i_low = mid; + *i_high = mid - 1; + } + if (data->comp[mid].ohm > ohm) { + *i_low = mid + 1; + *i_high = mid; + } + + return 0; +} + +static int get_temp_mC(struct ntc_data *data, unsigned int ohm, int *temp) +{ + int low, high; + int ret; + + ret = lookup_comp(data, ohm, &low, &high); + if (ret) { + /* Unable to use linear approximation */ + if (low != -1) + *temp = data->comp[low].temp_C * 1000; + else if (high != -1) + *temp = data->comp[high].temp_C * 1000; + else + return ret; + } else { + *temp = data->comp[low].temp_C * 1000 + + ((data->comp[high].temp_C - data->comp[low].temp_C) * + 1000 * ((int)ohm - (int)data->comp[low].ohm)) / + ((int)data->comp[high].ohm - (int)data->comp[low].ohm); + } + + return 0; +} + +static int ntc_thermistor_read(struct ntc_data *data, int *temp) +{ + int ret; + int read_ohm, read_uV; + unsigned int ohm = 0; + + if (data->pdata->read_ohm) { + read_ohm = data->pdata->read_ohm(); + if (read_ohm < 0) + return read_ohm; + ohm = (unsigned int)read_ohm; + } + + if (data->pdata->read_uV) { + read_uV = data->pdata->read_uV(); + if (read_uV < 0) + return read_uV; + ohm = get_ohm_of_thermistor(data, (unsigned int)read_uV); + } + + ret = get_temp_mC(data, ohm, temp); + if (ret) { + dev_dbg(data->dev, "Sensor reading function not available.\n"); + return ret; + } + + return 0; +} + +static ssize_t ntc_show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ntc_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} + +static ssize_t ntc_show_type(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "4\n"); +} + +static ssize_t ntc_show_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ntc_data *data = dev_get_drvdata(dev); + int temp, ret; + + ret = ntc_thermistor_read(data, &temp); + if (ret) + return ret; + return sprintf(buf, "%d\n", temp); +} + +static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO, ntc_show_type, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, ntc_show_temp, NULL, 0); +static DEVICE_ATTR(name, S_IRUGO, ntc_show_name, NULL); + +static struct attribute *ntc_attributes[] = { + &dev_attr_name.attr, + &sensor_dev_attr_temp1_type.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ntc_attr_group = { + .attrs = ntc_attributes, +}; + +static int __devinit ntc_thermistor_probe(struct platform_device *pdev) +{ + struct ntc_data *data; + struct ntc_thermistor_platform_data *pdata = pdev->dev.platform_data; + int ret = 0; + + if (!pdata) { + dev_err(&pdev->dev, "No platform init data supplied.\n"); + return -ENODEV; + } + + /* Either one of the two is required. */ + if (!pdata->read_uV && !pdata->read_ohm) { + dev_err(&pdev->dev, "Both read_uV and read_ohm missing." + "Need either one of the two.\n"); + return -EINVAL; + } + + if (pdata->read_uV && pdata->read_ohm) { + dev_warn(&pdev->dev, "Only one of read_uV and read_ohm " + "is needed; ignoring read_uV.\n"); + pdata->read_uV = NULL; + } + + if (pdata->read_uV && (pdata->pullup_uV == 0 || + (pdata->pullup_ohm == 0 && pdata->connect == + NTC_CONNECTED_GROUND) || + (pdata->pulldown_ohm == 0 && pdata->connect == + NTC_CONNECTED_POSITIVE) || + (pdata->connect != NTC_CONNECTED_POSITIVE && + pdata->connect != NTC_CONNECTED_GROUND))) { + dev_err(&pdev->dev, "Required data to use read_uV not " + "supplied.\n"); + return -EINVAL; + } + + data = kzalloc(sizeof(struct ntc_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = &pdev->dev; + data->pdata = pdata; + strncpy(data->name, pdev->id_entry->name, PLATFORM_NAME_SIZE); + + switch (pdev->id_entry->driver_data) { + case TYPE_NCPXXWB473: + data->comp = ncpXXwb473; + data->n_comp = ARRAY_SIZE(ncpXXwb473); + break; + case TYPE_NCPXXWL333: + data->comp = ncpXXwl333; + data->n_comp = ARRAY_SIZE(ncpXXwl333); + break; + default: + dev_err(&pdev->dev, "Unknown device type: %lu(%s)\n", + pdev->id_entry->driver_data, + pdev->id_entry->name); + ret = -EINVAL; + goto err; + } + + platform_set_drvdata(pdev, data); + + ret = sysfs_create_group(&data->dev->kobj, &ntc_attr_group); + if (ret) { + dev_err(data->dev, "unable to create sysfs files\n"); + goto err; + } + + data->hwmon_dev = hwmon_device_register(data->dev); + if (IS_ERR_OR_NULL(data->hwmon_dev)) { + dev_err(data->dev, "unable to register as hwmon device.\n"); + ret = -EINVAL; + goto err_after_sysfs; + } + + dev_info(&pdev->dev, "Thermistor %s:%d (type: %s/%lu) successfully probed.\n", + pdev->name, pdev->id, pdev->id_entry->name, + pdev->id_entry->driver_data); + return 0; +err_after_sysfs: + sysfs_remove_group(&data->dev->kobj, &ntc_attr_group); +err: + kfree(data); + return ret; +} + +static int __devexit ntc_thermistor_remove(struct platform_device *pdev) +{ + struct ntc_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&data->dev->kobj, &ntc_attr_group); + platform_set_drvdata(pdev, NULL); + + kfree(data); + + return 0; +} + +static const struct platform_device_id ntc_thermistor_id[] = { + { "ncp15wb473", TYPE_NCPXXWB473 }, + { "ncp18wb473", TYPE_NCPXXWB473 }, + { "ncp21wb473", TYPE_NCPXXWB473 }, + { "ncp03wb473", TYPE_NCPXXWB473 }, + { "ncp15wl333", TYPE_NCPXXWL333 }, + { }, +}; + +static struct platform_driver ntc_thermistor_driver = { + .driver = { + .name = "ntc-thermistor", + .owner = THIS_MODULE, + }, + .probe = ntc_thermistor_probe, + .remove = __devexit_p(ntc_thermistor_remove), + .id_table = ntc_thermistor_id, +}; + +static int __init ntc_thermistor_init(void) +{ + return platform_driver_register(&ntc_thermistor_driver); +} + +module_init(ntc_thermistor_init); + +static void __exit ntc_thermistor_cleanup(void) +{ + platform_driver_unregister(&ntc_thermistor_driver); +} + +module_exit(ntc_thermistor_cleanup); + +MODULE_DESCRIPTION("NTC Thermistor Driver"); +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ntc-thermistor"); diff --git a/include/linux/platform_data/ntc_thermistor.h b/include/linux/platform_data/ntc_thermistor.h new file mode 100644 index 000000000000..abd286215279 --- /dev/null +++ b/include/linux/platform_data/ntc_thermistor.h @@ -0,0 +1,53 @@ +/* + * ntc_thermistor.h - NTC Thermistors + * + * Copyright (C) 2010 Samsung Electronics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _LINUX_NTC_H +#define _LINUX_NTC_H + +enum ntc_thermistor_type { + TYPE_NCPXXWB473, + TYPE_NCPXXWL333, +}; + +struct ntc_thermistor_platform_data { + /* + * One (not both) of read_uV and read_ohm should be provided and only + * one of the two should be provided. + * Both functions should return negative value for an error case. + * + * pullup_uV, pullup_ohm, pulldown_ohm, and connect are required to use + * read_uV() + * + * How to setup pullup_ohm, pulldown_ohm, and connect is + * described at Documentation/hwmon/ntc + * + * pullup/down_ohm: 0 for infinite / not-connected + */ + int (*read_uV)(void); + unsigned int pullup_uV; + + unsigned int pullup_ohm; + unsigned int pulldown_ohm; + enum { NTC_CONNECTED_POSITIVE, NTC_CONNECTED_GROUND } connect; + + int (*read_ohm)(void); +}; + +#endif /* _LINUX_NTC_H */ -- cgit v1.2.3