blob: 4477030855ce336afa7f8e5489173a1fb4ae87b7 [file] [log] [blame]
/*
* c2_adc.c
* Driver for the Chorus2 ADC block.
* Copyright (C) 2010,2012 Imagination Technologies Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/ioctl.h>
#include <linux/errno.h>
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/semaphore.h>
#include <linux/mm.h>
#include <asm/soc-chorus2/c2_irqnums.h>
#include <asm/irq.h>
#include <asm/img_dma.h>
#include "chorus2_adc.h"
#define DRV_VERSION "1.0"
#define DRV_NAME "c2_adc"
/* Size of the buffer */
#define BUFFER_SIZE (1 << 16) /* 32kb */
#define MAX_BUF_OUT (BUFFER_SIZE >> 1) /* half BUFFER_SIZE */
struct c2_adc_priv {
/* Device pointer */
struct miscdevice *dev;
int irq;
/* User lock */
spinlock_t user_lock;
struct semaphore read_sem;
/* User count & id */
int user_count;
int user_id;
/* Current speed of ADC clock (8.192 - 24.576 MHz) */
int adc_clk_speed;
/* DMA buffer */
int chan_out;
void *buf_virt;
dma_addr_t buf_phys;
unsigned int *dma_desc;
dma_addr_t dma_desc_phys;
/* Double buffers */
int buffer_number;
void *buf1;
void *buf2;
void *current_buffer;
spinlock_t buffer_lock;
/* Register block */
unsigned int *reg_base;
};
static struct c2_adc_priv c2_adc_dev;
/* Reset the clocks */
static void c2_adc_reset(void)
{
unsigned int flags;
/* Bring ADC/SCP to known initial values */
TBI_LOCK(flags);
/* Put the SCP into reset */
scp_write(SCP_CTRL_RESETN, CONTROL);
/* Turn off digital ADC clock */
writel(DCXO_CLK_ADC_DISABLE, DCXO_CLK_ENABLE);
/* Set ADC Clock to 8.192MHz */
writel(ADC_CLK_8192M, ADC_CLK_SEL_ADDR);
/* Enable digital ADC clock for SCP */
writel(DCXO_CLK_ADC_ENABLE, DCXO_CLK_ENABLE);
/* Set SCP input clock to use clock gen */
writel(USE_ON_CHIP_IF_CLK, SCP_IF_PIN_CTRL);
/* Initialise the SCP to a known state (out of reset) */
scp_write(SCP_CTRL_BYPASS | SCP_CTRL_DMA_SYNC_EN | SCP_CTRL_PWR_ON,
CONTROL);
TBI_UNLOCK(flags);
}
/* Initialise the DMA block */
static void c2_setup_dma(void)
{
unsigned int *c2_adc_dma_desc = c2_adc_dev.dma_desc;
/* Setup the dma list descriptor */
c2_adc_dma_desc[0] = 1 << 30;
c2_adc_dma_desc[1] = (1 << 30) | ((MAX_BUF_OUT >> 2) & 0xffff);
c2_adc_dma_desc[2] = SCP_OUTPUT_ADDR >> 2;
c2_adc_dma_desc[3] = 4 << 26;
c2_adc_dma_desc[4] = 0;
c2_adc_dma_desc[5] = 0;
c2_adc_dma_desc[6] = c2_adc_dev.buf_phys;
c2_adc_dma_desc[7] = c2_adc_dev.dma_desc_phys;
/* Setup the DMA to be ready to get going */
img_dma_set_list_addr(c2_adc_dev.chan_out, c2_adc_dev.dma_desc_phys);
wmb();
}
/*
* Open the device.
* This will only allow one file descriptor to be open for this device.
*/
static int c2_adc_open(struct inode *i, struct file *f)
{
int ret = 0;
/* Single-user exclusivity */
spin_lock(&(c2_adc_dev.user_lock));
/* Allow user, an 'su' user and root to access the device */
if (c2_adc_dev.user_count &&
(c2_adc_dev.user_id != current->real_cred->uid) &&
(c2_adc_dev.user_id != current->real_cred->euid) &&
!capable(CAP_DAC_OVERRIDE)) {
ret = -EBUSY;
goto done;
}
/* Ensure we have read access */
if ((f->f_flags & O_ACCMODE) == O_WRONLY) {
ret = -EINVAL;
goto done;
}
if (!c2_adc_dev.user_count) {
c2_adc_dev.user_id = current->real_cred->uid;
spin_unlock(&c2_adc_dev.user_lock);
memset(c2_adc_dev.buf1, 0, MAX_BUF_OUT);
memset(c2_adc_dev.buf2, 0, MAX_BUF_OUT);
img_dma_start_list(c2_adc_dev.chan_out);
spin_lock(&c2_adc_dev.user_lock);
}
c2_adc_dev.user_count++;
done:
spin_unlock(&c2_adc_dev.user_lock);
return ret;
}
/*
* Release the device.
*/
static int c2_adc_release(struct inode *i, struct file *f)
{
spin_lock(&(c2_adc_dev.user_lock));
c2_adc_dev.user_count--;
if (!c2_adc_dev.user_count) {
spin_unlock(&c2_adc_dev.user_lock);
img_dma_stop_list(c2_adc_dev.chan_out);
img_dma_reset(c2_adc_dev.chan_out);
c2_setup_dma();
goto done;
}
spin_unlock(&c2_adc_dev.user_lock);
done:
return 0;
}
/*
* Read data from the device
*/
static ssize_t c2_adc_read(struct file *f, char __user *buf,
size_t count, loff_t *off)
{
int ret = 0;
unsigned int *p = NULL;
if (down_interruptible(&(c2_adc_dev.read_sem)))
return -ERESTARTSYS;
if (*off >= MAX_BUF_OUT)
*off -= MAX_BUF_OUT;
if (*off + count > MAX_BUF_OUT)
count = MAX_BUF_OUT - *off;
spin_lock(&c2_adc_dev.buffer_lock);
p = c2_adc_dev.current_buffer + *off;
spin_unlock(&c2_adc_dev.buffer_lock);
if (copy_to_user(buf, p, count)) {
return -EFAULT;
goto out;
}
*off += count;
ret = count;
out:
up(&(c2_adc_dev.read_sem));
return ret;
}
static const struct file_operations adc_fops = {
.owner = THIS_MODULE,
.open = c2_adc_open,
.release = c2_adc_release,
.read = c2_adc_read,
};
static struct miscdevice c2_adc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "adc",
.fops = &adc_fops,
};
/* IRQ */
static irqreturn_t c2_adc_irq(int irq, void *data)
{
unsigned int status = 0;
img_dma_get_int_status(c2_adc_dev.chan_out, &status);
if (status & 0x20000)
memcpy(c2_adc_dev.current_buffer, c2_adc_dev.buf_virt,
MAX_BUF_OUT);
spin_lock(&(c2_adc_dev.buffer_lock));
/* Switch buffer to DMA into */
c2_adc_dev.buffer_number = (c2_adc_dev.buffer_number + 1) & 0x01;
c2_adc_dev.current_buffer = (c2_adc_dev.buffer_number) ?
c2_adc_dev.buf1 : c2_adc_dev.buf2;
spin_unlock(&(c2_adc_dev.buffer_lock));
img_dma_set_int_status(c2_adc_dev.chan_out, 0);
return IRQ_HANDLED;
}
/* Module initialisation */
static int c2_adc_probe(void)
{
int ret = 0;
pr_info("%s: Chorus2 Analogue-to-Digital Converter driver V%s\n",
DRV_NAME, DRV_VERSION);
/* Remap the peripheral memory */
c2_adc_dev.reg_base = ioremap(SCP_BASE, 0x0fff);
if (!c2_adc_dev.reg_base) {
pr_err("%s: Unable to remap IO registers, aborting\n",
DRV_NAME);
ret = -ENXIO;
goto done_err;
}
/* Initial device reset */
c2_adc_reset();
/* Initialise the open spinlock */
spin_lock_init(&(c2_adc_dev.user_lock));
spin_lock_init(&c2_adc_dev.buffer_lock);
sema_init(&(c2_adc_dev.read_sem), 1);
ret = misc_register(&c2_adc_device);
if (ret) {
pr_err("%s: Unable to register device node, aborting\n",
DRV_NAME);
goto done_unmap;
}
c2_adc_dev.dev = &c2_adc_device;
/* Set coherent DMA mask */
ret = dma_set_coherent_mask(c2_adc_device.this_device, 0xffffffff);
if (ret) {
pr_err("%s: Unable to initialise DMA, aborting\n", DRV_NAME);
goto done_unreg;
}
/* We can now do stuff with our device - first, allocate DMA out */
ret = img_request_dma(11, SCP_DMA_OUT_PERIPH);
if (ret < 0) {
pr_err("%s: Unable to allocate outbound DMA channel, aborting\n",
DRV_NAME);
goto done_unreg;
}
c2_adc_dev.chan_out = ret;
img_dma_reset(c2_adc_dev.chan_out);
/* Allocate the buffer */
c2_adc_dev.buf_virt = dma_alloc_coherent(c2_adc_device.this_device,
BUFFER_SIZE, &(c2_adc_dev.buf_phys), GFP_KERNEL);
if (!c2_adc_dev.buf_virt) {
pr_err("%s: Unable to allocate outbound DMA buffer, aborting\n",
DRV_NAME);
ret = -ENOMEM;
goto done_free_dma;
}
c2_adc_dev.dma_desc = dma_alloc_coherent(c2_adc_device.this_device,
8 * sizeof(unsigned int),
&c2_adc_dev.dma_desc_phys, GFP_KERNEL);
if (!c2_adc_dev.dma_desc) {
pr_err("%s: Unable to allocate DMA descriptor buffer, aborting\n",
DRV_NAME);
ret = -ENOMEM;
goto done_free_coherent1;
}
c2_setup_dma();
/* Setup the bounce buffers */
c2_adc_dev.buf1 = kzalloc(MAX_BUF_OUT, GFP_KERNEL);
if (!c2_adc_dev.buf1) {
pr_err("%s: Unable to allocate primary buffer, aborting\n",
DRV_NAME);
ret = -ENOMEM;
goto done_free_coherent2;
}
c2_adc_dev.buf2 = kzalloc(MAX_BUF_OUT, GFP_KERNEL);
if (!c2_adc_dev.buf2) {
pr_err("%s: Unable to allocate secondary buffer, aborting\n",
DRV_NAME);
ret = -ENOMEM;
goto done_free_buffer;
}
/*
* Initialise bounce pointer - due to the way the DMAC works, this
* should be the 2nd buffer (interrupts trigger BEFORE the list
* element has been processed)
*/
c2_adc_dev.current_buffer = c2_adc_dev.buf1;
c2_adc_dev.irq = img_dma_get_irq(c2_adc_dev.chan_out);
if (c2_adc_dev.irq < 0) {
pr_err("%s: Unable to get DMA IRQ\n", DRV_NAME);
ret = c2_adc_dev.irq;
goto done_free_buffer;
}
/* Setup the IRQ */
ret = request_irq(c2_adc_dev.irq, c2_adc_irq, 0, "c2_adc", &c2_adc_dev);
if (ret) {
pr_err("%s: Unable to allocate IRQ, aborting\n", DRV_NAME);
ret = -EBUSY;
goto done_free_buffer;
}
/* We're done now :) */
pr_info("%s: Device allocated at /dev/adc\n", DRV_NAME);
goto done;
done_free_buffer:
kfree(c2_adc_dev.buf2);
kfree(c2_adc_dev.buf1);
done_free_coherent2:
dma_free_coherent(c2_adc_device.this_device, 8,
c2_adc_dev.dma_desc,
c2_adc_dev.dma_desc_phys);
done_free_coherent1:
dma_free_coherent(c2_adc_device.this_device, BUFFER_SIZE,
c2_adc_dev.buf_virt, c2_adc_dev.buf_phys);
done_free_dma:
img_free_dma(c2_adc_dev.chan_out);
done_unreg:
misc_deregister(&c2_adc_device);
done_unmap:
iounmap(c2_adc_dev.reg_base);
done_err:
pr_err("%s: Error code: %d", DRV_NAME, ret);
done:
return ret;
}
module_init(c2_adc_probe);
static void c2_adc_remove(void)
{
free_irq(c2_adc_dev.irq, &c2_adc_dev);
kfree(c2_adc_dev.buf2);
kfree(c2_adc_dev.buf1);
dma_free_coherent(c2_adc_device.this_device, 8,
c2_adc_dev.dma_desc,
c2_adc_dev.dma_desc_phys);
dma_free_coherent(c2_adc_device.this_device, BUFFER_SIZE,
c2_adc_dev.buf_virt, c2_adc_dev.buf_phys);
img_free_dma(c2_adc_dev.chan_out);
misc_deregister(&c2_adc_device);
iounmap(c2_adc_dev.reg_base);
return;
}
module_exit(c2_adc_remove);
MODULE_DESCRIPTION("Chorus2 ADC driver");
MODULE_AUTHOR("Imagination Technologies");
MODULE_LICENSE("GPL");