From 843cb7a97dbb96d7064d6a5f30d80b68ccc599a4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E7=BD=97=E4=BC=9F?= Date: Sat, 15 May 2010 07:42:12 +0000 Subject: [PATCH] luowei add adc driver and adckey driver on 100515 --- arch/arm/mach-rk2818/board-midsdk.c | 31 ++ .../arm/mach-rk2818/include/mach/rk2818_adc.h | 56 +++ drivers/input/keyboard/Kconfig | 9 + drivers/input/keyboard/rk2818_adckey.c | 352 +++++++++++++ drivers/staging/Kconfig | 1 + drivers/staging/Makefile | 1 + drivers/staging/rk2818/Kconfig | 14 + drivers/staging/rk2818/Makefile | 1 + drivers/staging/rk2818/rk2818_adc.c | 469 ++++++++++++++++++ 9 files changed, 934 insertions(+) create mode 100644 arch/arm/mach-rk2818/include/mach/rk2818_adc.h create mode 100644 drivers/input/keyboard/rk2818_adckey.c create mode 100644 drivers/staging/rk2818/Kconfig create mode 100644 drivers/staging/rk2818/Makefile create mode 100644 drivers/staging/rk2818/rk2818_adc.c diff --git a/arch/arm/mach-rk2818/board-midsdk.c b/arch/arm/mach-rk2818/board-midsdk.c index 07d127be01a5..f83e4bf10876 100644 --- a/arch/arm/mach-rk2818/board-midsdk.c +++ b/arch/arm/mach-rk2818/board-midsdk.c @@ -245,6 +245,35 @@ static struct spi_board_info board_spi_devices[] = { }, }; +/*ADC*/ +static struct resource rk2818_adc_resource[] = { + { + .start = IRQ_NR_ADC, + .end = IRQ_NR_ADC, + .flags = IORESOURCE_IRQ, + }, + { + .start = RK2818_ADC_PHYS, + .end = RK2818_ADC_PHYS + RK2818_ADC_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + +}; + +struct platform_device rk2818_device_adc = { + .name = "rk2818-adc", + .id = -1, + .num_resources = ARRAY_SIZE(rk2818_adc_resource), + .resource = rk2818_adc_resource, +}; + + +struct platform_device rk2818_device_adckey = { + .name = "rk2818-adckey", + .id = -1, + .dev.parent = &rk2818_device_adc.dev, +}; + /*rk2818_fb gpio information*/ static struct rk2818_fb_gpio rk2818_fb_gpio_info = { .lcd_cs = 0, @@ -278,6 +307,8 @@ static struct platform_device *devices[] __initdata = { &rk2818_device_i2c1, #endif &rk2818_device_spim, + &rk2818_device_adc, + &rk2818_device_adckey, &rk2818_device_fb, }; diff --git a/arch/arm/mach-rk2818/include/mach/rk2818_adc.h b/arch/arm/mach-rk2818/include/mach/rk2818_adc.h new file mode 100644 index 000000000000..e3df6fd6a45e --- /dev/null +++ b/arch/arm/mach-rk2818/include/mach/rk2818_adc.h @@ -0,0 +1,56 @@ +/* arch/arm/mach-rk28418/include/mach/rk2818_adc.h + * + * Copyright (c) 2010 luowei + * + * This program is free software; yosu 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 __ASM_ARCH_RK28_ADC_H +#define __ASM_ARCH_RK28_ADC_H + +#define RK28_ADCREG(x) (x) +#define RK28_ADCDAT RK28_ADCREG(0x00) +#define RK28_ADCSTAS RK28_ADCREG(0x04) +#define RK28_ADCCON RK28_ADCREG(0x08) + +//ADC_DATA +#define RK28_ADC_DATA_MASK (0x3ff) + +//ADC_STAS +#define RK28_ADC_STAS_STOP (0) + +//ADC_CTRL +#define RK28_ADC_INT_END (1<<6) +#define RK28_ADC_INT_CLEAR (~(1<<6)) +#define RK28_ADC_INT_ENABLE (1<<5) +#define RK28_ADC_INT_DISABLE (~(1<<5)) +#define RK28_ADC_CONV_START (1<<4) +#define RK28_ADC_CONV_STOP (~(1<<4)) +#define RK28_ADC_POWER_UP (1<<3) +#define RK28_ADC_POWER_DOWN (~(1<<3)) +#define RK28_ADC_SEL_CH(x) ((x)&0x03) +#define RK28_ADC_MASK_CH (~(0x03)) + +struct rk28_adc_client; + +extern int rk28_adc_start(struct rk28_adc_client *client, + unsigned int channel, unsigned int nr_samples); + +extern int rk28_adc_read(struct rk28_adc_client *client, unsigned int ch); + +extern struct rk28_adc_client * + rk28_adc_register(struct platform_device *pdev, + void (*select)(struct rk28_adc_client *client, + unsigned selected), + void (*conv)(struct rk28_adc_client *client, + unsigned d0, unsigned d1, + unsigned *samples_left), + unsigned int is_ts); + +extern void rk28_adc_release(struct rk28_adc_client *client); + +#endif + diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index ee98b1bc5d89..5003bda21d01 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -422,5 +422,14 @@ config KEYBOARD_W90P910 To compile this driver as a module, choose M here: the module will be called w90p910_keypad. +config KEYBOARD_RK28ADC + tristate "RK2818 ADC Keypad support" + depends on ARCH_RK2818 + default y + help + Say Y here to enable the adc keypad on SDK board + based on RK2818. + To compile this driver as a module, choose M here: the + module will be called rk2818_adckey. endif diff --git a/drivers/input/keyboard/rk2818_adckey.c b/drivers/input/keyboard/rk2818_adckey.c new file mode 100644 index 000000000000..3f7a839b6505 --- /dev/null +++ b/drivers/input/keyboard/rk2818_adckey.c @@ -0,0 +1,352 @@ +/* + * linux/drivers/input/keyboard/rk28_adckey.c + * + * This driver program support to AD key which use for rk28 chip + * + * 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 +#include +#include + +#include + +#if 0 +#define DBG(x...) printk(x) +#else +#define DBG(x...) +#endif + +//ROCKCHIP AD KEY CODE ,for demo board +// key ---> EV +#define AD2KEY1 KEY_ESC +#define AD2KEY2 KEY_7 +#define AD2KEY3 KEY_8 +#define AD2KEY4 KEY_9 +#define AD2KEY5 KEY_0 +#define AD2KEY6 KEY_W + +#define Valuedrift 50 +#define ADEmpty 1000 +#define ADKEYNum 6 + +#define ADKEYCH 1 //ADͨµÀ + +#define KEY_PHYS_NAME "rk2818 adc key" + +volatile int ADSampleTimes = 0; +volatile int ADC_Chanel = 0; +volatile int ADC_Value[4]={ADEmpty+1, ADEmpty+1, ADEmpty+1, ADEmpty+1}; //0->ch0 1->ch1 2->ch2 3->ch3 + +//ADC Registers +typedef struct tagADC_keyst +{ + unsigned int adc_value; + unsigned int adc_keycode; +}ADC_keyst,*pADC_keyst; + +// adc ---> key +static ADC_keyst advaluetab[] = +{ + {95, AD2KEY1}, + {249, AD2KEY2}, + {406, AD2KEY3}, + {561, AD2KEY4}, + {726, AD2KEY5}, + {899, AD2KEY6}, + {ADEmpty,0} +}; + +//key code tab +static unsigned char initkey_code[6] = +{ + AD2KEY1,AD2KEY2,AD2KEY3,AD2KEY4,AD2KEY5,AD2KEY6 +}; + + +struct rk28_adckey +{ + struct semaphore lock; + struct rk28_adc_client *client; + struct input_dev *input_dev; + struct timer_list timer; + unsigned char keycodes[6]; + + struct clk *clk; + void __iomem *mmio_base; +}; + +struct rk28_adckey *prk28_adckey; + +unsigned int rk28_get_keycode(unsigned int advalue,pADC_keyst ptab) +{ + while(ptab->adc_value != ADEmpty) + { + if((advalue > ptab->adc_value - Valuedrift) && (advalue < ptab->adc_value + Valuedrift)) + return ptab->adc_keycode; + ptab++; + } + + return 0; +} + +static int rk28_adckey_open(struct input_dev *dev) +{ + //struct rk28_adckey *adckey = input_get_drvdata(dev); + + return 0; +} + +static void rk28_adckey_close(struct input_dev *dev) +{ + //struct rk28_adckey *adckey = input_get_drvdata(dev); +// +} + +#ifdef CONFIG_PM +static int rk28_adckey_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct rk28_adckey *adckey = platform_get_drvdata(pdev); + + clk_disable(adckey->clk); + return 0; +} + +static int rk28_adckey_resume(struct platform_device *pdev) +{ + struct rk28_adckey *adckey = platform_get_drvdata(pdev); + struct input_dev *input_dev = adckey->input_dev; + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) { + /* Enable unit clock */ + clk_enable(adckey->clk); + } + + mutex_unlock(&input_dev->mutex); + + return 0; +} +#else +#define rk28_adckey_suspend NULL +#define rk28_adckey_resume NULL +#endif + +//read four ADC chanel +static int rk28_read_adc(struct rk28_adckey *adckey) +{ + int ret; + + ret = down_interruptible(&adckey->lock); + if (ret < 0) + return ret; + if(ADC_Chanel > 3) + ADC_Chanel = 0; + ADC_Value[ADC_Chanel] = rk28_adc_read(adckey->client, ADC_Chanel); + DBG("Enter::%s,LINE=%d,ADC_Value[%d]=%d\n",__FUNCTION__,__LINE__,ADC_Chanel,ADC_Value[ADC_Chanel]); + ADC_Chanel++; + up(&adckey->lock); + return ret; +} + +static void rk28_adkeyscan_timer(unsigned long data) +{ + unsigned int adcvalue = -1, code; + + prk28_adckey->timer.expires = jiffies+2; + add_timer(&prk28_adckey->timer); +#if 1 + if (ADSampleTimes < 2) + { + ADSampleTimes ++; + return; + } + + ADSampleTimes = 0; +#endif + + rk28_read_adc(prk28_adckey); + adcvalue = ADC_Value[ADKEYCH]; + if(adcvalue > ADEmpty) + return; + printk("adcvalue=0x%x\n",adcvalue); + + code=rk28_get_keycode(adcvalue,advaluetab); + if(code) + { + switch(code) + { + case AD2KEY1: + printk("KEY1\n"); + break; + case AD2KEY2: + printk("KEY2\n"); + break; + case AD2KEY3: + printk("KEY3\n"); + break; + case AD2KEY4: + printk("KEY4\n"); + break; + case AD2KEY5: + printk("KEY5\n"); + break; + case AD2KEY6: + printk("KEY6\n"); + break; + default: + printk("unknow\n"); + break; + } + input_report_key(prk28_adckey->input_dev,code,1); + input_sync(prk28_adckey->input_dev); + input_report_key(prk28_adckey->input_dev,code,0); + input_sync(prk28_adckey->input_dev); + return; + } + + +} + + +static int __devinit rk28_adckey_probe(struct platform_device *pdev) +{ + struct rk28_adckey *adckey; + struct input_dev *input_dev; + int error,i; + + adckey = kzalloc(sizeof(struct rk28_adckey), GFP_KERNEL); + if (adckey == NULL) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + memcpy(adckey->keycodes, initkey_code, sizeof(adckey->keycodes)); + + /* Create and register the input driver. */ + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&pdev->dev, "failed to allocate input device\n"); + error = -ENOMEM; + goto failed_put_clk; + } + + input_dev->name = pdev->name; + //input_dev->id.bustype = BUS_HOST; + input_dev->open = rk28_adckey_open; + input_dev->close = rk28_adckey_close; + input_dev->dev.parent = &pdev->dev; + input_dev->phys = KEY_PHYS_NAME; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + + input_dev->keycode = adckey->keycodes; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(initkey_code); + for (i = 0; i < ARRAY_SIZE(initkey_code); i++) + set_bit(initkey_code[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + adckey->input_dev = input_dev; + input_set_drvdata(input_dev, adckey); + + input_dev->evbit[0] = BIT_MASK(EV_KEY) ; + +// rk28_adckey_build_keycode(adckey); + platform_set_drvdata(pdev, adckey); + + init_MUTEX(&adckey->lock); + + /* Register with the core ADC driver. */ + adckey->client = rk28_adc_register(pdev, NULL, NULL, 0); + if (IS_ERR(adckey->client)) { + dev_err(&pdev->dev, "cannot register adc\n"); + error = PTR_ERR(adckey->client); + goto failed_free; + } + + prk28_adckey = adckey; + + /* Register the input device */ + error = input_register_device(input_dev); + if (error) { + dev_err(&pdev->dev, "failed to register input device\n"); + goto failed_free_dev; + } + + setup_timer(&adckey->timer, rk28_adkeyscan_timer, (unsigned long)adckey); + //mod_timer(&adckey->timer, 100); + adckey->timer.expires = jiffies+30; + add_timer(&adckey->timer); + printk("Enter::%s,LINE=%d\n",__FUNCTION__,__LINE__); + return 0; + +failed_free_dev: + platform_set_drvdata(pdev, NULL); + input_free_device(input_dev); +failed_put_clk: + //clk_put(adckey->clk); +failed_free: + kfree(adckey); + return error; +} + +static int __devexit rk28_adckey_remove(struct platform_device *pdev) +{ + struct rk28_adckey *adckey = platform_get_drvdata(pdev); + + input_unregister_device(adckey->input_dev); + input_free_device(adckey->input_dev); + platform_set_drvdata(pdev, NULL); + rk28_adc_release(adckey->client); + kfree(adckey); + return 0; +} + +static struct platform_driver rk28_adckey_driver = +{ + .probe = rk28_adckey_probe, + .remove = __devexit_p(rk28_adckey_remove), + .suspend = rk28_adckey_suspend, + .resume = rk28_adckey_resume, + .driver = { + .name = "rk2818-adckey", + .owner = THIS_MODULE, + }, +}; + + int __init rk28_adckey_init(void) +{ + printk("Enter::%s,LINE=%d\n",__FUNCTION__,__LINE__); + return platform_driver_register(&rk28_adckey_driver); +} + +static void __exit rk28_adckey_exit(void) +{ + platform_driver_unregister(&rk28_adckey_driver); +} + +module_init(rk28_adckey_init); +module_exit(rk28_adckey_exit); + +MODULE_DESCRIPTION("rk2818 adc Key Controller Driver"); +MODULE_AUTHOR("luowei lw@rock-chips.com"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index d21b3469f6d7..5b8df3df0498 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -125,5 +125,6 @@ source "drivers/staging/sep/Kconfig" source "drivers/staging/iio/Kconfig" +source "drivers/staging/rk2818/Kconfig" endif # !STAGING_EXCLUDE_BUILD endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index b516268957d0..0da3c0b529c2 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -44,3 +44,4 @@ obj-$(CONFIG_VME_BUS) += vme/ obj-$(CONFIG_RAR_REGISTER) += rar/ obj-$(CONFIG_DX_SEP) += sep/ obj-$(CONFIG_IIO) += iio/ +obj-$(CONFIG_RK28) += rk28/ diff --git a/drivers/staging/rk2818/Kconfig b/drivers/staging/rk2818/Kconfig new file mode 100644 index 000000000000..558c75a37d82 --- /dev/null +++ b/drivers/staging/rk2818/Kconfig @@ -0,0 +1,14 @@ +# +# RK28XX ADC configuration +# +menuconfig RK28 + tristate "RK28 Staging Driver" + depends on ARCH_RK2818 + +if RK28 +config RK28_ADC + tristate "RK28 ADC Driver" + default y + ---help--- + RK28 ADC Driver +endif #RK28 diff --git a/drivers/staging/rk2818/Makefile b/drivers/staging/rk2818/Makefile new file mode 100644 index 000000000000..632089443594 --- /dev/null +++ b/drivers/staging/rk2818/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_RK28_ADC) += rk2818_adc.o diff --git a/drivers/staging/rk2818/rk2818_adc.c b/drivers/staging/rk2818/rk2818_adc.c new file mode 100644 index 000000000000..9fb5b8ed4eb3 --- /dev/null +++ b/drivers/staging/rk2818/rk2818_adc.c @@ -0,0 +1,469 @@ +/* arch/arm/plat-s3c24xx/adc.c + * + * Copyright (c) 2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks , + * + * S3C24XX ADC device core + * + * 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. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* This driver is designed to control the usage of the ADC block between + * the keyboard and any other drivers that may need to use it. + * + * Priority will be given to the touchscreen driver, but as this itself is + * rate limited it should not starve other requests which are processed in + * order that they are received. + * + * Each user registers to get a client block which uniquely identifies it + * and stores information such as the necessary functions to callback when + * action is required. + */ + +struct rk28_adc_client { + struct platform_device *pdev; + struct list_head pend; + wait_queue_head_t *wait; + + unsigned int nr_samples; + int result; + unsigned char is_ts; + unsigned char channel; + + void (*select_cb)(struct rk28_adc_client *c, unsigned selected); + void (*convert_cb)(struct rk28_adc_client *c, + unsigned val1, unsigned val2, + unsigned *samples_left); +}; + +struct adc_device { + struct platform_device *pdev; + struct platform_device *owner; + struct clk *clk; + struct rk28_adc_client *cur; + struct rk28_adc_client *ts_pend; + void __iomem *regs; + + unsigned int pre_con; + int irq; +}; + +static struct adc_device *adc_dev; + +static LIST_HEAD(adc_pending); + +#define adc_dbg(_adc, msg...) dev_dbg(&(_adc)->pdev->dev, msg) + +static inline void rk28_adc_int_enable(struct adc_device *adc) +{ + unsigned con = readl(adc->regs + RK28_ADCCON); + con |= RK28_ADC_INT_ENABLE; + writel(con, adc->regs + RK28_ADCCON); +} + +static inline void rk28_adc_int_disable(struct adc_device *adc) +{ + unsigned con = readl(adc->regs + RK28_ADCCON); + con &= RK28_ADC_INT_DISABLE; + writel(con, adc->regs + RK28_ADCCON); +} + +static inline void rk28_adc_int_clear(struct adc_device *adc) +{ + unsigned con = readl(adc->regs + RK28_ADCCON); + con &= RK28_ADC_INT_CLEAR; + writel(con, adc->regs + RK28_ADCCON); +} + +static inline void rk28_adc_power_up(struct adc_device *adc) +{ + unsigned con = readl(adc->regs + RK28_ADCCON); + con |= RK28_ADC_POWER_UP; + writel(con, adc->regs + RK28_ADCCON); +} + +static inline void rk28_adc_power_down(struct adc_device *adc) +{ + unsigned con = readl(adc->regs + RK28_ADCCON); + con &= RK28_ADC_POWER_DOWN; + writel(con, adc->regs + RK28_ADCCON); +} + +static inline void rk28_adc_convert(struct adc_device *adc) +{ + unsigned con = readl(adc->regs + RK28_ADCCON); + con |= RK28_ADC_POWER_UP | RK28_ADC_CONV_START | RK28_ADC_INT_ENABLE; + writel(con, adc->regs + RK28_ADCCON); +} + +static inline void rk28_adc_select(struct adc_device *adc, + struct rk28_adc_client *client) +{ + unsigned con = readl(adc->regs + RK28_ADCCON); + + client->select_cb(client, 1); + + con &= RK28_ADC_MASK_CH; + con &= RK28_ADC_CONV_STOP; + + if (!client->is_ts) + con |= RK28_ADC_SEL_CH(client->channel); + + writel(con, adc->regs + RK28_ADCCON); +} + +static void rk28_adc_dbgshow(struct adc_device *adc) +{ + adc_dbg(adc, "CON=0x%x, STAS=0x%x, DATA=0x%x\n", + readl(adc->regs + RK28_ADCCON), + readl(adc->regs + RK28_ADCSTAS), + readl(adc->regs + RK28_ADCDAT)); +} + +static void rk28_adc_try(struct adc_device *adc) +{ + struct rk28_adc_client *next = adc->ts_pend; + + if (!next && !list_empty(&adc_pending)) { + next = list_first_entry(&adc_pending, + struct rk28_adc_client, pend); + list_del(&next->pend); + } else + adc->ts_pend = NULL; + + if (next) { + adc_dbg(adc, "new client is %p\n", next); + adc->cur = next; + rk28_adc_select(adc, next); + rk28_adc_convert(adc); + rk28_adc_dbgshow(adc); + } +} + +int rk28_adc_start(struct rk28_adc_client *client, + unsigned int channel, unsigned int nr_samples) +{ + struct adc_device *adc = adc_dev; + unsigned long flags; + + if (!adc) { + printk(KERN_ERR "%s: failed to find adc\n", __func__); + return -EINVAL; + } + + if (client->is_ts && adc->ts_pend) + return -EAGAIN; + + local_irq_save(flags); + + client->channel = channel; + client->nr_samples = nr_samples; + + if (client->is_ts) + adc->ts_pend = client; + else + list_add_tail(&client->pend, &adc_pending); + + if (!adc->cur) + rk28_adc_try(adc); + local_irq_restore(flags); + + return 0; +} +EXPORT_SYMBOL_GPL(rk28_adc_start); + +static void rk28_convert_done(struct rk28_adc_client *client, + unsigned v, unsigned u, unsigned *left) +{ + client->result = v; + wake_up(client->wait); +} + +int rk28_adc_read(struct rk28_adc_client *client, unsigned int ch) +{ + DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake); + int ret; + + client->convert_cb = rk28_convert_done; + client->wait = &wake; + client->result = -1; + + ret = rk28_adc_start(client, ch, 1); + if (ret < 0) + goto err; + + ret = wait_event_timeout(wake, client->result >= 0, HZ / 2); + if (client->result < 0) { + ret = -ETIMEDOUT; + goto err; + } + + client->convert_cb = NULL; + return client->result; + +err: + return ret; +} +EXPORT_SYMBOL_GPL(rk28_adc_read); + +static void rk28_adc_default_select(struct rk28_adc_client *client, + unsigned select) +{ +} + +struct rk28_adc_client *rk28_adc_register(struct platform_device *pdev, + void (*select)(struct rk28_adc_client *client, + unsigned int selected), + void (*conv)(struct rk28_adc_client *client, + unsigned d0, unsigned d1, + unsigned *samples_left), + unsigned int is_ts) +{ + struct rk28_adc_client *client; + + WARN_ON(!pdev); + + if (!select) + select = rk28_adc_default_select; + + if (!pdev) + return ERR_PTR(-EINVAL); + + client = kzalloc(sizeof(struct rk28_adc_client), GFP_KERNEL); + if (!client) { + dev_err(&pdev->dev, "no memory for adc client\n"); + return ERR_PTR(-ENOMEM); + } + + client->pdev = pdev; + client->is_ts = is_ts; + client->select_cb = select; + client->convert_cb = conv; + + return client; +} +EXPORT_SYMBOL_GPL(rk28_adc_register); + +void rk28_adc_release(struct rk28_adc_client *client) +{ + /* We should really check that nothing is in progress. */ + if (adc_dev->cur == client) + adc_dev->cur = NULL; + if (adc_dev->ts_pend == client) + adc_dev->ts_pend = NULL; + else { + struct list_head *p, *n; + struct rk28_adc_client *tmp; + + list_for_each_safe(p, n, &adc_pending) { + tmp = list_entry(p, struct rk28_adc_client, pend); + if (tmp == client) + list_del(&tmp->pend); + } + } + + if (adc_dev->cur == NULL) + rk28_adc_try(adc_dev); + kfree(client); +} +EXPORT_SYMBOL_GPL(rk28_adc_release); + +static irqreturn_t rk28_adc_irq(int irq, void *pw) +{ + struct adc_device *adc = pw; + struct rk28_adc_client *client = adc->cur; + unsigned long flags; + unsigned data0, data1 = 0; + rk28_adc_int_disable(adc); + rk28_adc_int_clear(adc); + + if (!client) { + dev_warn(&adc->pdev->dev, "%s: no adc pending\n", __func__); + return IRQ_HANDLED; + } + + data0 = readl(adc->regs + RK28_ADCDAT); + + adc_dbg(adc, "read %d: 0x%x\n", client->nr_samples, data0); + + client->nr_samples--; + + if (client->convert_cb) + (client->convert_cb)(client, data0 & 0x3ff, data1 & 0x3ff, + &client->nr_samples); + + if (client->nr_samples > 0) { + /* fire another conversion for this */ + + client->select_cb(client, 1); + rk28_adc_convert(adc); + } else { + local_irq_save(flags); + (client->select_cb)(client, 0); + adc->cur = NULL; + + rk28_adc_try(adc); + local_irq_restore(flags); + } + + return IRQ_HANDLED; +} + +static int rk28_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct adc_device *adc; + struct resource *regs; + int ret; + + adc = kzalloc(sizeof(struct adc_device), GFP_KERNEL); + if (adc == NULL) { + dev_err(dev, "failed to allocate adc_device\n"); + return -ENOMEM; + } + + adc->pdev = pdev; + + adc->irq = platform_get_irq(pdev, 0); + if (adc->irq <= 0) { + dev_err(dev, "failed to get adc irq\n"); + ret = -ENOENT; + goto err_alloc; + } + + ret = request_irq(adc->irq, rk28_adc_irq, 0, dev_name(dev), adc); + if (ret < 0) { + dev_err(dev, "failed to attach adc irq\n"); + goto err_alloc; + } + + adc->clk = clk_get(dev, "lsadc"); + if (IS_ERR(adc->clk)) { + dev_err(dev, "failed to get adc clock\n"); + ret = PTR_ERR(adc->clk); + goto err_irq; + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) { + dev_err(dev, "failed to find registers\n"); + ret = -ENXIO; + goto err_clk; + } + + adc->regs = ioremap(regs->start, resource_size(regs)); + if (!adc->regs) { + dev_err(dev, "failed to map registers\n"); + ret = -ENXIO; + goto err_clk; + } + + clk_enable(adc->clk); + + dev_info(dev, "attached adc driver\n"); + + platform_set_drvdata(pdev, adc); + + rk28_adc_int_disable(adc); + adc_dev = adc; + printk("Enter::%s,LINE=%d\n",__FUNCTION__,__LINE__); + return 0; + + err_clk: + clk_put(adc->clk); + + err_irq: + free_irq(adc->irq, adc); + + err_alloc: + kfree(adc); + return ret; +} + +static int rk28_adc_remove(struct platform_device *pdev) +{ + struct adc_device *adc = platform_get_drvdata(pdev); + rk28_adc_power_down(adc); + iounmap(adc->regs); + free_irq(adc->irq, adc); + clk_disable(adc->clk); + clk_put(adc->clk); + kfree(adc); + + return 0; +} + +#ifdef CONFIG_PM +static int rk28_adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct adc_device *adc = platform_get_drvdata(pdev); + u32 con; + + con = readl(adc->regs + RK28_ADCCON); + adc->pre_con = con; + con &= RK28_ADC_POWER_DOWN; + writel(con, adc->regs + RK28_ADCCON); + + clk_disable(adc->clk); + + return 0; +} + +static int rk28_adc_resume(struct platform_device *pdev) +{ + struct adc_device *adc = platform_get_drvdata(pdev); + + clk_enable(adc->clk); + + writel(adc->pre_con, adc->regs + RK28_ADCCON); + return 0; +} + +#else +#define rk28_adc_suspend NULL +#define rk28_adc_resume NULL +#endif + +static struct platform_driver rk28_adc_driver = { + .driver = { + .name = "rk2818-adc", + .owner = THIS_MODULE, + }, + .probe = rk28_adc_probe, + .remove = __devexit_p(rk28_adc_remove), + .suspend = rk28_adc_suspend, + .resume = rk28_adc_resume, +}; + +static int __init adc_init(void) +{ + int ret; + + ret = platform_driver_register(&rk28_adc_driver); + if (ret) + printk(KERN_ERR "%s: failed to add adc driver\n", __func__); + printk("Enter::%s,LINE=%d\n",__FUNCTION__,__LINE__); + return ret; +} + +module_init(adc_init); +MODULE_DESCRIPTION("rk28xx adc driver"); +MODULE_AUTHOR("luowei lw@rock-chips.com"); +MODULE_LICENSE("GPL"); + -- 2.34.1