mirror of
https://github.com/torvalds/linux.git
synced 2026-06-08 14:42:37 +02:00
add adc driver(path: drivers/adc)
This commit is contained in:
parent
1cd9d57991
commit
e94c69a05c
|
|
@ -561,7 +561,9 @@ static struct platform_device *devices[] __initdata = {
|
|||
&rk29xx_device_spi0m,
|
||||
&rk29xx_device_spi1m,
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_ADC_RK29
|
||||
&rk29_device_adc,
|
||||
#endif
|
||||
#ifdef CONFIG_I2C0_RK29
|
||||
&rk29_device_i2c0,
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -21,6 +21,29 @@
|
|||
#include <mach/rk29_iomap.h>
|
||||
#include <mach/rk29-dma-pl330.h>
|
||||
#include "devices.h"
|
||||
#ifdef CONFIG_ADC_RK29
|
||||
static struct resource rk29_adc_resource[] = {
|
||||
{
|
||||
.start = IRQ_SARADC,
|
||||
.end = IRQ_SARADC,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = RK29_ADC_PHYS,
|
||||
.end = RK29_ADC_PHYS + RK29_ADC_SIZE - 1,
|
||||
.flags = IORESOURCE_MEM,
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
struct platform_device rk29_device_adc = {
|
||||
.name = "rk29-adc",
|
||||
.id = -1,
|
||||
.num_resources = ARRAY_SIZE(rk29_adc_resource),
|
||||
.resource = rk29_adc_resource,
|
||||
};
|
||||
|
||||
#endif
|
||||
#ifdef CONFIG_I2C_RK29
|
||||
static struct resource resources_i2c0[] = {
|
||||
{
|
||||
|
|
|
|||
|
|
@ -39,5 +39,6 @@ extern struct rk29_sdmmc_platform_data default_sdmmc0_data;
|
|||
extern struct rk29_sdmmc_platform_data default_sdmmc1_data;
|
||||
extern struct platform_device rk29_device_sdmmc0;
|
||||
extern struct platform_device rk29_device_sdmmc1;
|
||||
extern struct platform_device rk29_device_adc;
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ source "drivers/i2c/Kconfig"
|
|||
|
||||
source "drivers/spi/Kconfig"
|
||||
|
||||
source "drivers/adc/Kconfig"
|
||||
|
||||
source "drivers/fpga/Kconfig"
|
||||
|
||||
source "drivers/headset_observe/Kconfig"
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ obj-$(CONFIG_INPUT) += input/
|
|||
obj-$(CONFIG_I2O) += message/
|
||||
obj-$(CONFIG_RTC_LIB) += rtc/
|
||||
obj-y += i2c/ media/
|
||||
obj-y += adc/
|
||||
obj-$(CONFIG_PPS) += pps/
|
||||
obj-$(CONFIG_W1) += w1/
|
||||
obj-$(CONFIG_POWER_SUPPLY) += power/
|
||||
|
|
|
|||
12
drivers/adc/Kconfig
Normal file
12
drivers/adc/Kconfig
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#
|
||||
# ADC subsystem configuration
|
||||
#
|
||||
|
||||
menuconfig ADC
|
||||
bool "ADC support"
|
||||
default y
|
||||
if ADC
|
||||
|
||||
source drivers/adc/plat/Kconfig
|
||||
|
||||
endif # ADC
|
||||
7
drivers/adc/Makefile
Normal file
7
drivers/adc/Makefile
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Makefile for the adc core.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_ADC) += core.o
|
||||
obj-y += plat/
|
||||
|
||||
211
drivers/adc/core.c
Executable file
211
drivers/adc/core.c
Executable file
|
|
@ -0,0 +1,211 @@
|
|||
/* drivers/adc/core.c
|
||||
*
|
||||
* 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 <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/adc.h>
|
||||
|
||||
|
||||
static struct adc_host *g_adc = NULL;
|
||||
|
||||
struct adc_host *adc_alloc_host(int extra, struct device *dev)
|
||||
{
|
||||
struct adc_host *adc;
|
||||
|
||||
adc = kzalloc(sizeof(struct adc_host) + extra, GFP_KERNEL);
|
||||
if (!adc)
|
||||
return NULL;
|
||||
adc->dev = dev;
|
||||
g_adc = adc;
|
||||
return adc;
|
||||
}
|
||||
EXPORT_SYMBOL(adc_alloc_host);
|
||||
void adc_free_host(struct adc_host *adc)
|
||||
{
|
||||
kfree(adc);
|
||||
adc = NULL;
|
||||
return;
|
||||
}
|
||||
EXPORT_SYMBOL(adc_free_host);
|
||||
|
||||
|
||||
struct adc_client *adc_register(int chn,
|
||||
void (*callback)(struct adc_client *, void *, int),
|
||||
void *callback_param)
|
||||
{
|
||||
struct adc_client *client;
|
||||
if(!g_adc)
|
||||
{
|
||||
printk(KERN_ERR "adc host has not initialized\n");
|
||||
return NULL;
|
||||
}
|
||||
if(chn >= MAX_ADC_CHN)
|
||||
{
|
||||
dev_err(g_adc->dev, "channel[%d] is greater than the maximum[%d]\n", chn, MAX_ADC_CHN);
|
||||
return NULL;
|
||||
}
|
||||
client = kzalloc(sizeof(struct adc_client), GFP_KERNEL);
|
||||
if(!client)
|
||||
{
|
||||
dev_err(g_adc->dev, "no memory for adc client\n");
|
||||
return NULL;
|
||||
}
|
||||
client->callback = callback;
|
||||
client->callback_param = callback_param;
|
||||
client->chn = chn;
|
||||
|
||||
client->adc = g_adc;
|
||||
|
||||
return client;
|
||||
}
|
||||
EXPORT_SYMBOL(adc_register);
|
||||
|
||||
void adc_unregister(struct adc_client *client)
|
||||
{
|
||||
kfree(client);
|
||||
client = NULL;
|
||||
return;
|
||||
}
|
||||
EXPORT_SYMBOL(adc_unregister);
|
||||
|
||||
|
||||
static void trigger_next_adc_job_if_any(struct adc_host *adc)
|
||||
{
|
||||
int head = adc->queue_head;
|
||||
|
||||
if (!adc->queue[head])
|
||||
return;
|
||||
adc->cur = adc->queue[head]->client;
|
||||
adc->ops->start(adc);
|
||||
return;
|
||||
}
|
||||
|
||||
static int
|
||||
adc_enqueue_request(struct adc_host *adc, struct adc_request *req)
|
||||
{
|
||||
int head, tail;
|
||||
|
||||
mutex_lock(&adc->queue_mutex);
|
||||
|
||||
head = adc->queue_head;
|
||||
tail = adc->queue_tail;
|
||||
|
||||
if (adc->queue[tail]) {
|
||||
mutex_unlock(&adc->queue_mutex);
|
||||
dev_err(adc->dev, "ADC queue is full, dropping request\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
adc->queue[tail] = req;
|
||||
if (head == tail)
|
||||
trigger_next_adc_job_if_any(adc);
|
||||
adc->queue_tail = (tail + 1) & (MAX_ADC_FIFO_DEPTH - 1);
|
||||
|
||||
mutex_unlock(&adc->queue_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
adc_sync_read_callback(struct adc_client *client, void *param, int result)
|
||||
{
|
||||
struct adc_request *req = param;
|
||||
|
||||
client->result = result;
|
||||
complete(&req->completion);
|
||||
}
|
||||
|
||||
int adc_sync_read(struct adc_client *client)
|
||||
{
|
||||
struct adc_request *req = NULL;
|
||||
int err;
|
||||
|
||||
if(client == NULL) {
|
||||
printk(KERN_ERR "client point is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
if(client->adc->is_suspended == 1) {
|
||||
dev_dbg(client->adc->dev, "system enter sleep\n");
|
||||
return -1;
|
||||
}
|
||||
req = kzalloc(sizeof(*req), GFP_KERNEL);
|
||||
if (!req){
|
||||
dev_err(client->adc->dev, "no memory for adc request\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
req->chn = client->chn;
|
||||
req->callback = adc_sync_read_callback;
|
||||
req->callback_param = req;
|
||||
req->client = client;
|
||||
|
||||
init_completion(&req->completion);
|
||||
err = adc_enqueue_request(client->adc, req);
|
||||
if (err)
|
||||
{
|
||||
dev_err(client->adc->dev, "fail to enqueue request\n");
|
||||
kfree(req);
|
||||
return err;
|
||||
}
|
||||
wait_for_completion(&req->completion);
|
||||
return client->result;
|
||||
}
|
||||
EXPORT_SYMBOL(adc_sync_read);
|
||||
|
||||
int adc_async_read(struct adc_client *client)
|
||||
{
|
||||
struct adc_request *req = NULL;
|
||||
|
||||
if(client == NULL) {
|
||||
printk(KERN_ERR "client point is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
if(client->adc->is_suspended == 1) {
|
||||
dev_dbg(client->adc->dev, "system enter sleep\n");
|
||||
return -1;
|
||||
}
|
||||
req = kzalloc(sizeof(*req), GFP_KERNEL);
|
||||
if (!req) {
|
||||
dev_err(client->adc->dev, "no memory for adc request\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
req->chn = client->chn;
|
||||
req->callback = client->callback;
|
||||
req->callback_param = client->callback_param;
|
||||
req->client = client;
|
||||
|
||||
return adc_enqueue_request(client->adc, req);
|
||||
}
|
||||
EXPORT_SYMBOL(adc_async_read);
|
||||
|
||||
void adc_core_irq_handle(struct adc_host *adc)
|
||||
{
|
||||
struct adc_request *req;
|
||||
int head, res;
|
||||
|
||||
head = adc->queue_head;
|
||||
|
||||
req = adc->queue[head];
|
||||
if (WARN_ON(!req)) {
|
||||
dev_err(adc->dev, "adc irq: ADC queue empty!\n");
|
||||
return;
|
||||
}
|
||||
adc->queue[head] = NULL;
|
||||
adc->queue_head = (head + 1) & (MAX_ADC_FIFO_DEPTH - 1);
|
||||
|
||||
res = adc->ops->read(adc);
|
||||
adc->ops->stop(adc);
|
||||
trigger_next_adc_job_if_any(adc);
|
||||
|
||||
req->callback(adc->cur, req->callback_param, res);
|
||||
kfree(req);
|
||||
}
|
||||
EXPORT_SYMBOL(adc_core_irq_handle);
|
||||
|
||||
|
||||
16
drivers/adc/plat/Kconfig
Normal file
16
drivers/adc/plat/Kconfig
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# Adc hardware configuration
|
||||
#
|
||||
|
||||
choice
|
||||
prompt "ADC hardware drivers"
|
||||
config ADC_RK28
|
||||
bool "RK28 adc interface"
|
||||
help
|
||||
This supports the use of the ADC interface on rk28 processors.
|
||||
|
||||
config ADC_RK29
|
||||
bool "RK29 adc interface"
|
||||
help
|
||||
This supports the use of the ADC interface on rk29 processors.
|
||||
endchoice
|
||||
6
drivers/adc/plat/Makefile
Normal file
6
drivers/adc/plat/Makefile
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#
|
||||
# Makefile for the adc hardware drivers.
|
||||
#
|
||||
obj-$(CONFIG_ADC_RK28) += rk28_adc.o
|
||||
obj-$(CONFIG_ADC_RK29) += rk29_adc.o
|
||||
|
||||
236
drivers/adc/plat/rk28_adc.c
Executable file
236
drivers/adc/plat/rk28_adc.c
Executable file
|
|
@ -0,0 +1,236 @@
|
|||
/* drivers/adc/chips/rk28_adc.c
|
||||
*
|
||||
* 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 <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/adc.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
|
||||
#include "rk28_adc.h"
|
||||
|
||||
//#define ADC_TEST
|
||||
|
||||
struct rk28_adc_device {
|
||||
int irq;
|
||||
void __iomem *regs;
|
||||
struct clk * clk;
|
||||
struct resource *ioarea;
|
||||
struct adc_host *adc;
|
||||
};
|
||||
static void rk28_adc_start(struct adc_host *adc)
|
||||
{
|
||||
struct rk28_adc_device *dev = adc_priv(adc);
|
||||
int chn = adc->cur->chn;
|
||||
|
||||
writel(ADC_CTRL_IRQ_ENABLE|ADC_CTRL_POWER_UP|ADC_CTRL_START|ADC_CTRL_CH(chn),
|
||||
dev->regs + ADC_CTRL);
|
||||
}
|
||||
static void rk28_adc_stop(struct adc_host *adc)
|
||||
{
|
||||
struct rk28_adc_device *dev = adc_priv(adc);
|
||||
|
||||
writel(ADC_CTRL_IRQ_STATUS, dev->regs + ADC_CTRL);
|
||||
}
|
||||
static int rk28_adc_read(struct adc_host *adc)
|
||||
{
|
||||
struct rk28_adc_device *dev = adc_priv(adc);
|
||||
|
||||
udelay(SAMPLE_RATE);
|
||||
return readl(dev->regs + ADC_DATA) & ADC_DATA_MASK;
|
||||
}
|
||||
static irqreturn_t rk28_adc_irq(int irq, void *data)
|
||||
{
|
||||
struct rk28_adc_device *dev = data;
|
||||
adc_core_irq_handle(dev->adc);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
static const struct adc_ops rk28_adc_ops = {
|
||||
.start = rk28_adc_start,
|
||||
.stop = rk28_adc_stop,
|
||||
.read = rk28_adc_read,
|
||||
};
|
||||
#ifdef ADC_TEST
|
||||
static void callback(struct adc_client *client, void *param, int result)
|
||||
{
|
||||
dev_info(client->adc->dev, "[chn%d] async_read = %d\n", client->chn, result);
|
||||
return;
|
||||
}
|
||||
static int rk28_adc_test(void)
|
||||
{
|
||||
int sync_read = 0;
|
||||
struct adc_client *client = adc_register(1, callback, NULL);
|
||||
|
||||
while(1)
|
||||
{
|
||||
adc_async_read(client);
|
||||
udelay(20);
|
||||
sync_read = adc_sync_read(client);
|
||||
dev_info(client->adc->dev, "[chn%d] sync_read = %d\n", client->chn, sync_read);
|
||||
udelay(20);
|
||||
}
|
||||
adc_unregister(client);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int rk28_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct adc_host *adc = NULL;
|
||||
struct rk28_adc_device *dev;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
adc = adc_alloc_host(sizeof(struct rk28_adc_device), &pdev->dev);
|
||||
if (!adc)
|
||||
return -ENOMEM;
|
||||
mutex_init(&adc->queue_mutex);
|
||||
adc->dev = &pdev->dev;
|
||||
adc->is_suspended = 0;
|
||||
adc->ops = &rk28_adc_ops;
|
||||
dev = adc_priv(adc);
|
||||
dev->adc = adc;
|
||||
dev->irq = platform_get_irq(pdev, 0);
|
||||
if (dev->irq <= 0) {
|
||||
dev_err(&pdev->dev, "failed to get adc irq\n");
|
||||
ret = -ENOENT;
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
ret = request_irq(dev->irq, rk28_adc_irq, 0, pdev->name, dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to attach adc irq\n");
|
||||
goto err_alloc;
|
||||
}
|
||||
dev->clk = clk_get(&pdev->dev, "saradc");
|
||||
if (IS_ERR(dev->clk)) {
|
||||
dev_err(&pdev->dev, "failed to get adc clock\n");
|
||||
ret = PTR_ERR(dev->clk);
|
||||
goto err_irq;
|
||||
}
|
||||
|
||||
ret = clk_set_rate(dev->clk, ADC_CLK_RATE * 1000 * 1000);
|
||||
if(ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to set adc clk\n");
|
||||
goto err_clk;
|
||||
}
|
||||
clk_enable(dev->clk);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "cannot find IO resource\n");
|
||||
ret = -ENOENT;
|
||||
goto err_clk;
|
||||
}
|
||||
dev->ioarea = request_mem_region(res->start, (res->end - res->start) + 1,
|
||||
pdev->name);
|
||||
if(dev->ioarea == NULL) {
|
||||
dev_err(&pdev->dev, "cannot request IO\n");
|
||||
ret = -ENXIO;
|
||||
goto err_clk;
|
||||
}
|
||||
dev->regs = ioremap(res->start, (res->end - res->start) + 1);
|
||||
if (!dev->regs) {
|
||||
dev_err(&pdev->dev, "cannot map IO\n");
|
||||
ret = -ENXIO;
|
||||
goto err_ioarea;
|
||||
}
|
||||
platform_set_drvdata(pdev, dev);
|
||||
dev_info(&pdev->dev, "rk28 adc: driver initialized\n");
|
||||
#ifdef ADC_TEST
|
||||
rk28_adc_test();
|
||||
#endif
|
||||
return 0;
|
||||
// err_iomap:
|
||||
// iounmap(dev->regs);
|
||||
|
||||
err_ioarea:
|
||||
release_resource(dev->ioarea);
|
||||
kfree(dev->ioarea);
|
||||
clk_disable(dev->clk);
|
||||
|
||||
err_clk:
|
||||
clk_put(dev->clk);
|
||||
|
||||
err_irq:
|
||||
free_irq(dev->irq, dev);
|
||||
|
||||
err_alloc:
|
||||
adc_free_host(dev->adc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rk28_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rk28_adc_device *dev = platform_get_drvdata(pdev);
|
||||
|
||||
iounmap(dev->regs);
|
||||
release_resource(dev->ioarea);
|
||||
kfree(dev->ioarea);
|
||||
free_irq(dev->irq, dev);
|
||||
clk_disable(dev->clk);
|
||||
clk_put(dev->clk);
|
||||
adc_free_host(dev->adc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int rk28_adc_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct rk28_adc_device *dev = platform_get_drvdata(pdev);
|
||||
|
||||
dev->adc->is_suspended = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk28_adc_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct rk28_adc_device *dev = platform_get_drvdata(pdev);
|
||||
|
||||
dev->adc->is_suspended = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#define rk28_adc_suspend NULL
|
||||
#define rk28_adc_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver rk28_adc_driver = {
|
||||
.driver = {
|
||||
.name = "rk28-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 rk28_adc_init(void)
|
||||
{
|
||||
return platform_driver_register(&rk28_adc_driver);
|
||||
}
|
||||
subsys_initcall(rk28_adc_init);
|
||||
|
||||
static void __exit rk28_adc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&rk28_adc_driver);
|
||||
}
|
||||
module_exit(rk28_adc_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Driver for ADC");
|
||||
MODULE_AUTHOR("kfx, kfx@rock-chips.com");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
32
drivers/adc/plat/rk28_adc.h
Executable file
32
drivers/adc/plat/rk28_adc.h
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
/* drivers/adc/chips/rk28_adc.h
|
||||
*
|
||||
* 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 __ASM_RK29_ADC_H
|
||||
#define __ASM_RK29_ADC_H
|
||||
|
||||
#define ADC_DATA 0x00
|
||||
#define ADC_DATA_MASK 0x3ff
|
||||
|
||||
#define ADC_STAS 0x04
|
||||
#define ADC_STAS_BUSY (1<<0)
|
||||
|
||||
#define ADC_CTRL 0x08
|
||||
#define ADC_CTRL_CH(ch) ((ch)<<0)
|
||||
#define ADC_CTRL_POWER_UP (1<<3)
|
||||
#define ADC_CTRL_START (1<<4)
|
||||
#define ADC_CTRL_IRQ_ENABLE (1<<5)
|
||||
#define ADC_CTRL_IRQ_STATUS (1<<6)
|
||||
|
||||
#define ADC_CLK_RATE 1 //1M
|
||||
/* maximum conversion rate of 100KSPS with 1MHZ ADC converter clock.
|
||||
* SET: real conversion rate is half of maximum conversion rate
|
||||
*/
|
||||
#define SAMPLE_RATE ((1000/100) * 2 /(ADC_CLK_RATE))
|
||||
|
||||
|
||||
#endif /* __ASM_RK29_ADC_H */
|
||||
236
drivers/adc/plat/rk29_adc.c
Executable file
236
drivers/adc/plat/rk29_adc.c
Executable file
|
|
@ -0,0 +1,236 @@
|
|||
/* drivers/adc/chips/rk29_adc.c
|
||||
*
|
||||
* 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 <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/adc.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
|
||||
#include "rk29_adc.h"
|
||||
|
||||
//#define ADC_TEST
|
||||
|
||||
struct rk29_adc_device {
|
||||
int irq;
|
||||
void __iomem *regs;
|
||||
struct clk * clk;
|
||||
struct resource *ioarea;
|
||||
struct adc_host *adc;
|
||||
};
|
||||
static void rk29_adc_start(struct adc_host *adc)
|
||||
{
|
||||
struct rk29_adc_device *dev = adc_priv(adc);
|
||||
int chn = adc->cur->chn;
|
||||
|
||||
writel(ADC_CTRL_IRQ_ENABLE|ADC_CTRL_POWER_UP|ADC_CTRL_START|ADC_CTRL_CH(chn),
|
||||
dev->regs + ADC_CTRL);
|
||||
}
|
||||
static void rk29_adc_stop(struct adc_host *adc)
|
||||
{
|
||||
struct rk29_adc_device *dev = adc_priv(adc);
|
||||
|
||||
writel(ADC_CTRL_IRQ_STATUS, dev->regs + ADC_CTRL);
|
||||
}
|
||||
static int rk29_adc_read(struct adc_host *adc)
|
||||
{
|
||||
struct rk29_adc_device *dev = adc_priv(adc);
|
||||
|
||||
udelay(SAMPLE_RATE);
|
||||
return readl(dev->regs + ADC_DATA) & ADC_DATA_MASK;
|
||||
}
|
||||
static irqreturn_t rk29_adc_irq(int irq, void *data)
|
||||
{
|
||||
struct rk29_adc_device *dev = data;
|
||||
adc_core_irq_handle(dev->adc);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
static const struct adc_ops rk29_adc_ops = {
|
||||
.start = rk29_adc_start,
|
||||
.stop = rk29_adc_stop,
|
||||
.read = rk29_adc_read,
|
||||
};
|
||||
#ifdef ADC_TEST
|
||||
static void callback(struct adc_client *client, void *param, int result)
|
||||
{
|
||||
dev_info(client->adc->dev, "[chn%d] async_read = %d\n", client->chn, result);
|
||||
return;
|
||||
}
|
||||
static int rk29_adc_test(void)
|
||||
{
|
||||
int sync_read = 0;
|
||||
struct adc_client *client = adc_register(1, callback, NULL);
|
||||
|
||||
while(1)
|
||||
{
|
||||
adc_async_read(client);
|
||||
udelay(20);
|
||||
sync_read = adc_sync_read(client);
|
||||
dev_info(client->adc->dev, "[chn%d] sync_read = %d\n", client->chn, sync_read);
|
||||
udelay(20);
|
||||
}
|
||||
adc_unregister(client);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int rk29_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct adc_host *adc = NULL;
|
||||
struct rk29_adc_device *dev;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
adc = adc_alloc_host(sizeof(struct rk29_adc_device), &pdev->dev);
|
||||
if (!adc)
|
||||
return -ENOMEM;
|
||||
mutex_init(&adc->queue_mutex);
|
||||
adc->dev = &pdev->dev;
|
||||
adc->is_suspended = 0;
|
||||
adc->ops = &rk29_adc_ops;
|
||||
dev = adc_priv(adc);
|
||||
dev->adc = adc;
|
||||
dev->irq = platform_get_irq(pdev, 0);
|
||||
if (dev->irq <= 0) {
|
||||
dev_err(&pdev->dev, "failed to get adc irq\n");
|
||||
ret = -ENOENT;
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
ret = request_irq(dev->irq, rk29_adc_irq, 0, pdev->name, dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to attach adc irq\n");
|
||||
goto err_alloc;
|
||||
}
|
||||
dev->clk = clk_get(&pdev->dev, "saradc");
|
||||
if (IS_ERR(dev->clk)) {
|
||||
dev_err(&pdev->dev, "failed to get adc clock\n");
|
||||
ret = PTR_ERR(dev->clk);
|
||||
goto err_irq;
|
||||
}
|
||||
|
||||
ret = clk_set_rate(dev->clk, ADC_CLK_RATE * 1000 * 1000);
|
||||
if(ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to set adc clk\n");
|
||||
goto err_clk;
|
||||
}
|
||||
clk_enable(dev->clk);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "cannot find IO resource\n");
|
||||
ret = -ENOENT;
|
||||
goto err_clk;
|
||||
}
|
||||
dev->ioarea = request_mem_region(res->start, (res->end - res->start) + 1,
|
||||
pdev->name);
|
||||
if(dev->ioarea == NULL) {
|
||||
dev_err(&pdev->dev, "cannot request IO\n");
|
||||
ret = -ENXIO;
|
||||
goto err_clk;
|
||||
}
|
||||
dev->regs = ioremap(res->start, (res->end - res->start) + 1);
|
||||
if (!dev->regs) {
|
||||
dev_err(&pdev->dev, "cannot map IO\n");
|
||||
ret = -ENXIO;
|
||||
goto err_ioarea;
|
||||
}
|
||||
platform_set_drvdata(pdev, dev);
|
||||
dev_info(&pdev->dev, "rk29 adc: driver initialized\n");
|
||||
#ifdef ADC_TEST
|
||||
rk29_adc_test();
|
||||
#endif
|
||||
return 0;
|
||||
// err_iomap:
|
||||
// iounmap(dev->regs);
|
||||
|
||||
err_ioarea:
|
||||
release_resource(dev->ioarea);
|
||||
kfree(dev->ioarea);
|
||||
clk_disable(dev->clk);
|
||||
|
||||
err_clk:
|
||||
clk_put(dev->clk);
|
||||
|
||||
err_irq:
|
||||
free_irq(dev->irq, dev);
|
||||
|
||||
err_alloc:
|
||||
adc_free_host(dev->adc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rk29_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rk29_adc_device *dev = platform_get_drvdata(pdev);
|
||||
|
||||
iounmap(dev->regs);
|
||||
release_resource(dev->ioarea);
|
||||
kfree(dev->ioarea);
|
||||
free_irq(dev->irq, dev);
|
||||
clk_disable(dev->clk);
|
||||
clk_put(dev->clk);
|
||||
adc_free_host(dev->adc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int rk29_adc_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct rk29_adc_device *dev = platform_get_drvdata(pdev);
|
||||
|
||||
dev->adc->is_suspended = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk29_adc_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct rk29_adc_device *dev = platform_get_drvdata(pdev);
|
||||
|
||||
dev->adc->is_suspended = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#define rk29_adc_suspend NULL
|
||||
#define rk29_adc_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver rk29_adc_driver = {
|
||||
.driver = {
|
||||
.name = "rk29-adc",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = rk29_adc_probe,
|
||||
.remove = __devexit_p(rk29_adc_remove),
|
||||
.suspend = rk29_adc_suspend,
|
||||
.resume = rk29_adc_resume,
|
||||
};
|
||||
|
||||
static int __init rk29_adc_init(void)
|
||||
{
|
||||
return platform_driver_register(&rk29_adc_driver);
|
||||
}
|
||||
subsys_initcall(rk29_adc_init);
|
||||
|
||||
static void __exit rk29_adc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&rk29_adc_driver);
|
||||
}
|
||||
module_exit(rk29_adc_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Driver for ADC");
|
||||
MODULE_AUTHOR("kfx, kfx@rock-chips.com");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
32
drivers/adc/plat/rk29_adc.h
Executable file
32
drivers/adc/plat/rk29_adc.h
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
/* drivers/adc/chips/rk29_adc.h
|
||||
*
|
||||
* 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 __ASM_RK29_ADC_H
|
||||
#define __ASM_RK29_ADC_H
|
||||
|
||||
#define ADC_DATA 0x00
|
||||
#define ADC_DATA_MASK 0x3ff
|
||||
|
||||
#define ADC_STAS 0x04
|
||||
#define ADC_STAS_BUSY (1<<0)
|
||||
|
||||
#define ADC_CTRL 0x08
|
||||
#define ADC_CTRL_CH(ch) ((ch)<<0)
|
||||
#define ADC_CTRL_POWER_UP (1<<3)
|
||||
#define ADC_CTRL_START (1<<4)
|
||||
#define ADC_CTRL_IRQ_ENABLE (1<<5)
|
||||
#define ADC_CTRL_IRQ_STATUS (1<<6)
|
||||
|
||||
#define ADC_CLK_RATE 1 //1M
|
||||
/* maximum conversion rate of 100KSPS with 1MHZ ADC converter clock.
|
||||
* SET: real conversion rate is half of maximum conversion rate
|
||||
*/
|
||||
#define SAMPLE_RATE ((1000/100) * 2 /(ADC_CLK_RATE))
|
||||
|
||||
|
||||
#endif /* __ASM_RK29_ADC_H */
|
||||
|
|
@ -478,7 +478,7 @@ static int rk29_xfer_msg(struct i2c_adapter *adap,
|
|||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
|
||||
|
|
|
|||
73
include/linux/adc.h
Executable file
73
include/linux/adc.h
Executable file
|
|
@ -0,0 +1,73 @@
|
|||
/* include/linux/adc.h
|
||||
*
|
||||
* 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 __ASM_ADC_CORE_H
|
||||
#define __ASM_ADC_CORE_H
|
||||
|
||||
#define MAX_ADC_CHN 4
|
||||
#define MAX_ADC_FIFO_DEPTH 8
|
||||
|
||||
|
||||
struct adc_client {
|
||||
int chn;
|
||||
int time;
|
||||
int result;
|
||||
void (*callback)(struct adc_client *, void *, int);
|
||||
void *callback_param;
|
||||
|
||||
struct adc_host *adc;
|
||||
};
|
||||
|
||||
struct adc_request {
|
||||
int result;
|
||||
int chn;
|
||||
void (*callback)(struct adc_client *, void *, int);
|
||||
void *callback_param;
|
||||
struct adc_client *client;
|
||||
/* Used in case of sync requests */
|
||||
struct completion completion;
|
||||
};
|
||||
struct adc_host;
|
||||
struct adc_ops {
|
||||
void (*start)(struct adc_host *);
|
||||
void (*stop)(struct adc_host *);
|
||||
int (*read)(struct adc_host *);
|
||||
};
|
||||
|
||||
|
||||
struct adc_host {
|
||||
struct device *dev;
|
||||
int is_suspended;
|
||||
struct adc_request *queue[MAX_ADC_FIFO_DEPTH];
|
||||
int queue_head;
|
||||
int queue_tail;
|
||||
struct mutex queue_mutex;
|
||||
struct adc_client *cur;
|
||||
const struct adc_ops *ops;
|
||||
unsigned long private[0];
|
||||
};
|
||||
static inline void *adc_priv(struct adc_host *adc)
|
||||
{
|
||||
return (void *)adc->private;
|
||||
}
|
||||
|
||||
extern struct adc_host *adc_alloc_host(int extra, struct device *dev);
|
||||
extern void adc_free_host(struct adc_host *adc);
|
||||
extern void adc_core_irq_handle(struct adc_host *adc);
|
||||
|
||||
|
||||
extern struct adc_client *adc_register(int chn,
|
||||
void (*callback)(struct adc_client *, void *, int),
|
||||
void *callback_param);
|
||||
extern void adc_unregister(struct adc_client *client);
|
||||
|
||||
extern int adc_sync_read(struct adc_client *client);
|
||||
extern int adc_async_read(struct adc_client *client);
|
||||
|
||||
#endif
|
||||
|
||||
Loading…
Reference in New Issue
Block a user