From 2035c3eea5d239d8380ba9beb507471992cfc71e Mon Sep 17 00:00:00 2001 From: Shijie Zhang Date: Fri, 6 Apr 2012 04:54:45 +0800 Subject: [PATCH] intel_basincove_gpadc: port MRFLD GPADC code to ICS BZ: 30664 Porting MRFLD GAPDC driver to ICS mainline branch. Change-Id: Ic1f6ef5e829bc502b4049e616254d53ba1288f2a Signed-off-by: Shijie Zhang Reviewed-on: http://android.intel.com:8080/42611 Reviewed-by: Du, Alek Reviewed-by: Yang, Bin Tested-by: Wang, Zhifeng Reviewed-by: buildbot Tested-by: buildbot --- arch/x86/configs/i386_mrfl_defconfig | 1 + arch/x86/include/asm/intel_basincove_gpadc.h | 41 ++++ drivers/hwmon/Kconfig | 9 + drivers/hwmon/Makefile | 1 + drivers/hwmon/intel_basincove_gpadc.c | 290 +++++++++++++++++++++++++++ 5 files changed, 342 insertions(+) create mode 100644 arch/x86/include/asm/intel_basincove_gpadc.h create mode 100644 drivers/hwmon/intel_basincove_gpadc.c diff --git a/arch/x86/configs/i386_mrfl_defconfig b/arch/x86/configs/i386_mrfl_defconfig index 9f1d8b0..4cb1712 100644 --- a/arch/x86/configs/i386_mrfl_defconfig +++ b/arch/x86/configs/i386_mrfl_defconfig @@ -1634,6 +1634,7 @@ CONFIG_SENSORS_CORETEMP=y # CONFIG_SENSORS_MS5607 is not set # CONFIG_SENSORS_LPS331AP is not set # CONFIG_MSIC_GPADC is not set +CONFIG_BASINCOVE_GPADC=y # CONFIG_MID_PWM is not set # CONFIG_SENSORS_MID_CURRENT is not set CONFIG_THERMAL=y diff --git a/arch/x86/include/asm/intel_basincove_gpadc.h b/arch/x86/include/asm/intel_basincove_gpadc.h new file mode 100644 index 0000000..ef2705d --- /dev/null +++ b/arch/x86/include/asm/intel_basincove_gpadc.h @@ -0,0 +1,41 @@ +#ifndef __INTEL_BASINCOVE_GPADC_H__ +#define __INTEL_BASINCOVE_GPADC_H__ + +#define GPADC_VBAT (1 << 0) +#define GPADC_BATID (1 << 1) +#define GPADC_IBAT (1 << 2) +#define GPADC_BATTEMP0 (1 << 3) +#define GPADC_BATTEMP1 (1 << 4) +#define GPADC_SYSTEMP0 (1 << 5) +#define GPADC_SYSTEMP1 (1 << 6) +#define GPADC_SYSTEMP2 (1 << 7) +#define GPADC_PMICTEMP (1 << 8) +#define GPADC_CH_NUM 9 + +#define MBATTEMP (1 << 2) +#define MSYSTEMP (1 << 3) +#define MBATT (1 << 4) +#define MVIBATT (1 << 5) +#define MCCTICK (1 << 7) + +#define GPADC_RSL(channel, res) \ + ({ \ + int order = -1; \ + int ch = channel; \ + do { \ + ch >>= 1; \ + order++; \ + } while (ch); \ + res->data[order]; \ + }) + +struct intel_basincove_gpadc_platform_data { + unsigned long intr; +}; + +struct gpadc_result { + int data[GPADC_CH_NUM]; +}; + +int intel_basincove_gpadc_sample(int ch, struct gpadc_result *res); +#endif diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 9646bd9..90738cf 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1440,6 +1440,15 @@ config MSIC_GPADC help Say Y here to enable MSIC GPADC driver on Intel Medfield Platform +config BASINCOVE_GPADC + tristate "BASINCOVE GPADC driver for Intel Merrifield platform" + depends on INTEL_SCU_IPC + help + Say Y here to enable Basin Cove GPADC driver on Intel Merrifield + Platform, and this driver is dependent on intel_scu_ipc driver for + pmic register access. + For details, pls refer to the source code. + config MID_PWM tristate "MID PWM driver for Intel Medfield platform" depends on INTEL_SCU_IPC diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 65ee3ba..fcfc1fe 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -126,6 +126,7 @@ obj-$(CONFIG_SENSORS_MS5607) += ms5607.o obj-$(CONFIG_SENSORS_L3G4200D_POLL) += l3g4200d_poll.o obj-$(CONFIG_SENSORS_LPS331AP) += lps331ap.o obj-$(CONFIG_MSIC_GPADC) += intel_mid_gpadc.o +obj-$(CONFIG_BASINCOVE_GPADC) += intel_basincove_gpadc.o obj-$(CONFIG_MID_PWM) += intel_mid_pwm.o obj-$(CONFIG_SENSORS_MID_CURRENT) += intel_mid_ocd.o obj-$(CONFIG_SENSORS_LSM303_MAG) += lsm303dlhc_compass.o diff --git a/drivers/hwmon/intel_basincove_gpadc.c b/drivers/hwmon/intel_basincove_gpadc.c new file mode 100644 index 0000000..9e4a98d --- /dev/null +++ b/drivers/hwmon/intel_basincove_gpadc.c @@ -0,0 +1,290 @@ +/* + * intel_basincove_gpadc.c - Intel Merrifield Basin Cove GPADC Driver + * + * Copyright (C) 2012 Intel Corporation + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Author: Bin Yang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPADCREQ 0xDC +#define GPADCREQ_IRQEN (1 << 1) +#define GPADCREQ_BUSY (1 << 0) +#define ADC1CNTL 0xDD +#define ADCIRQ 0x06 +#define MADCIRQ 0x11 + +static struct gpadc_regmap_t { + char *name; + int cntl; /* GPADC Conversion Control Bit indicator */ + int rslth; /* GPADC Conversion Result Register Addr High */ + int rsltl; /* GPADC Conversion Result Register Addr Low */ +} gpadc_regmaps[GPADC_CH_NUM] = { + {"VBAT", 5, 0xE9, 0xEA, }, + {"BATID", 4, 0xEB, 0xEC, }, + {"IBAT", 5, 0xED, 0xEE, }, + {"BATTEMP0", 2, 0xC8, 0xC9, }, + {"BATTEMP1", 2, 0xCA, 0xCB, }, + {"SYSTEMP0", 3, 0xC2, 0xC3, }, + {"SYSTEMP1", 3, 0xC4, 0xC5, }, + {"SYSTEMP2", 3, 0xC6, 0xC7, }, + {"PMICTEMP", 3, 0xCC, 0xCD, }, +}; + +struct gpadc_info { + int initialized; + /* This mutex protects gpadc sample/config from concurrent conflict. + Any function, which does the sample or config, needs to + hold this lock. + If it is locked, it also means the gpadc is in active mode. + */ + struct mutex lock; + struct device *dev; + int irq; + u8 irq_status; + wait_queue_head_t wait; + int sample_done; +}; + +static struct gpadc_info gpadc_info; + +static inline int gpadc_clear_bits(u16 addr, u8 mask) +{ + return intel_scu_ipc_update_register(addr, 0, mask); +} + +static inline int gpadc_set_bits(u16 addr, u8 mask) +{ + return intel_scu_ipc_update_register(addr, 0xff, mask); +} + +static inline int gpadc_write(u16 addr, u8 data) +{ + return intel_scu_ipc_iowrite8(addr, data); +} + +static inline int gpadc_read(u16 addr, u8 *data) +{ + return intel_scu_ipc_ioread8(addr, data); +} + +static int gpadc_busy_wait(void) +{ + u8 tmp; + int timeout = 0; + + gpadc_read(GPADCREQ, &tmp); + while (tmp & GPADCREQ_BUSY && timeout < 500) { + gpadc_read(GPADCREQ, &tmp); + usleep_range(1800, 2000); + timeout++; + } + + if (tmp & GPADCREQ_BUSY) + return -EBUSY; + else + return 0; +} + +static void gpadc_dump(struct gpadc_info *info) +{ + u8 tmp; + + dev_err(info->dev, "GPADC registers dump:\n"); + gpadc_read(ADCIRQ, &tmp); + dev_err(info->dev, "ADCIRQ: 0x%x\n", tmp); + gpadc_read(MADCIRQ, &tmp); + dev_err(info->dev, "MADCIRQ: 0x%x\n", tmp); + gpadc_read(GPADCREQ, &tmp); + dev_err(info->dev, "GPADCREQ: 0x%x\n", tmp); + gpadc_read(ADC1CNTL, &tmp); + dev_err(info->dev, "ADC1CNTL: 0x%x\n", tmp); +} + +static irqreturn_t gpadc_isr(int irq, void *data) +{ + struct gpadc_info *info = data; + + gpadc_read(GPADCREQ, &info->irq_status); + info->sample_done = 1; + wake_up(&info->wait); + return IRQ_HANDLED; +} + +/** + * intel_basincove_gpadc_sample - do gpadc sample. + * @ch: gpadc bit set of channels to sample, for example, set ch = (1<<0)|(1<<2) + * means you are going to sample both channel 0 and 2 at the same time. + * @res:gpadc sampling result + * + * Returns 0 on success or an error code. + * + * This function may sleep. + */ + +int intel_basincove_gpadc_sample(int ch, struct gpadc_result *res) +{ + struct gpadc_info *info = &gpadc_info; + int i, ret; + u8 tmp, th, tl; + + if (!info->initialized) + return -ENODEV; + + mutex_lock(&info->lock); + + tmp = GPADCREQ_IRQEN; + + for (i = 0; i < GPADC_CH_NUM; i++) { + if (ch & (1 << i)) + tmp |= (1 << gpadc_regmaps[i].cntl); + } + + info->sample_done = 0; + + ret = gpadc_busy_wait(); + if (ret) { + dev_err(info->dev, "GPADC is busy\n"); + goto done; + } + + gpadc_write(GPADCREQ, tmp); + + ret = wait_event_timeout(info->wait, info->sample_done, HZ); + if (ret == 0) { + gpadc_dump(info); + ret = -ETIMEDOUT; + dev_err(info->dev, "sample timeout, return %d\n", ret); + goto done; + } else { + ret = 0; + } + + for (i = 0; i < GPADC_CH_NUM; i++) { + if (ch & (1 << i)) { + gpadc_read(gpadc_regmaps[i].rslth, &th); + gpadc_read(gpadc_regmaps[i].rsltl, &tl); + res->data[i] = ((th & 0x3) << 8) + tl; + } + } + +done: + mutex_unlock(&info->lock); + return ret; +} +EXPORT_SYMBOL(intel_basincove_gpadc_sample); + +static int __devinit gpadc_probe(struct ipc_device *ipcdev) +{ + struct gpadc_info *info = &gpadc_info; + int err; + u8 mask; + + mutex_init(&info->lock); + init_waitqueue_head(&info->wait); + info->dev = &ipcdev->dev; + info->irq = ipc_get_irq(ipcdev, 0); + mask = MBATTEMP | MSYSTEMP | MBATT | MVIBATT | MCCTICK; + gpadc_clear_bits(MADCIRQ, mask); + + err = request_threaded_irq(info->irq, NULL, gpadc_isr, + IRQF_ONESHOT, "adc", info); + if (err) { + gpadc_dump(info); + dev_err(&ipcdev->dev, "unable to register irq %d\n", info->irq); + return err; + } + + info->initialized = 1; + return 0; +} + +static int __devexit gpadc_remove(struct ipc_device *ipcdev) +{ + struct gpadc_info *info = &gpadc_info; + free_irq(info->irq, info); + return 0; +} + +#ifdef CONFIG_PM +static int gpadc_suspend(struct device *dev) +{ + struct gpadc_info *info = &gpadc_info; + + if (mutex_trylock(&info->lock)) + return 0; + else + return -EBUSY; +} + +static int gpadc_resume(struct device *dev) +{ + struct gpadc_info *info = &gpadc_info; + + mutex_unlock(&info->lock); + return 0; +} +#else +#define gpadc_suspend NULL +#define gpadc_resume NULL +#endif + +static const struct dev_pm_ops gpadc_driver_pm_ops = { + .suspend = gpadc_suspend, + .resume = gpadc_resume, +}; + +static struct ipc_driver gpadc_driver = { + .driver = { + .name = "bcove_adc", + .owner = THIS_MODULE, + .pm = &gpadc_driver_pm_ops, + }, + .probe = gpadc_probe, + .remove = __devexit_p(gpadc_remove), +}; + +static int __init gpadc_module_init(void) +{ + return ipc_driver_register(&gpadc_driver); +} + +static void __exit gpadc_module_exit(void) +{ + ipc_driver_unregister(&gpadc_driver); +} + +module_init(gpadc_module_init); +module_exit(gpadc_module_exit); + +MODULE_AUTHOR("Yang Bin"); +MODULE_DESCRIPTION("Intel Merrifield Basin Cove GPADC Driver"); +MODULE_LICENSE("GPL"); -- 2.7.4