Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1759855Ab2EPIhR (ORCPT ); Wed, 16 May 2012 04:37:17 -0400 Received: from tx2ehsobe004.messaging.microsoft.com ([65.55.88.14]:33249 "EHLO tx2outboundpool.messaging.microsoft.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1759726Ab2EPIhD (ORCPT ); Wed, 16 May 2012 04:37:03 -0400 X-SpamScore: 0 X-BigFish: VPS0(zzzz1202hzz8275bh84d07hz2dh2a8h668h839hd24he5bh) X-Forefront-Antispam-Report: CIP:137.71.25.55;KIP:(null);UIP:(null);IPV:NLI;H:nwd2mta1.analog.com;RD:nwd2mail10.analog.com;EFVD:NLI From: To: Greg Kroah-Hartman , Arnd Bergmann CC: LKML , , Sonic Zhang Subject: [PATCH] driver: char: bfin_crc: CRC hardware accelerator driver for BF60x family processors. Date: Wed, 16 May 2012 16:26:07 +0800 Message-ID: <1337156767-13232-1-git-send-email-sonic.zhang@analog.com> X-Mailer: git-send-email 1.7.0.4 MIME-Version: 1.0 Content-Type: text/plain X-OriginatorOrg: analog.com Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 14879 Lines: 604 From: Sonic Zhang The CRC peripheral is a hardware block used to compute the CRC of the block of data. This is based on a CRC32 engine which computes the CRC value of 32b data words presented to it. For data words of < 32b in size, it is the responsibility of the application to pack the data into 32b data units. This driver defines data strcture crc_info and 4 IOCTL interfaces for applciations. 1) CRC Memory Scan 2) CRC Memory Transfer 3) CRC Data Verify 4) CRC Data Fill Signed-off-by: Sonic Zhang --- drivers/char/Kconfig | 8 + drivers/char/Makefile | 1 + drivers/char/bfin_crc.c | 537 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 546 insertions(+), 0 deletions(-) create mode 100644 drivers/char/bfin_crc.c diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index ee94686..69980ea 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -94,6 +94,14 @@ config BFIN_OTP_WRITE_ENABLE If unsure, say N. +config BFIN_CRC + tristate "Blackfin hardware CRC driver" + depends on BF60x + default y + help + This is a hardware CRC driver for BF60x. It is added to accelerate + CRC calculation individually or within DMA in user applications. + config PRINTER tristate "Parallel printer support" depends on PARPORT diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 0dc5d7c..500033e 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_VIOTAPE) += viotape.o obj-$(CONFIG_IBM_BSR) += bsr.o obj-$(CONFIG_SGI_MBCS) += mbcs.o obj-$(CONFIG_BFIN_OTP) += bfin-otp.o +obj-$(CONFIG_BFIN_CRC) += bfin_crc.o obj-$(CONFIG_PRINTER) += lp.o diff --git a/drivers/char/bfin_crc.c b/drivers/char/bfin_crc.c new file mode 100644 index 0000000..3f7d1d8 --- /dev/null +++ b/drivers/char/bfin_crc.c @@ -0,0 +1,537 @@ +/* + * Blackfin On-Chip hardware CRC Driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "bfin-crc" + +static LIST_HEAD(bfin_crc_list); + +static void bfin_crc_config_dma(unsigned long dma_ch, unsigned char *addr, + unsigned long size, unsigned long dma_config, int mod_dir) +{ + unsigned int dma_count; + unsigned int dma_shift; + int dma_mod; + + dma_config |= PSIZE_32; + + if ((unsigned long)addr % 4 == 0) { + dma_config |= WDSIZE_32; + dma_count = size >> 2; + dma_shift = 2; + } else if ((unsigned long)addr % 2 == 0) { + dma_config |= WDSIZE_16; + dma_count = size >> 1; + dma_shift = 1; + } else { + dma_config |= WDSIZE_8; + dma_count = size; + dma_shift = 0; + } + + dma_mod = (1 << dma_shift) * mod_dir; + if (mod_dir == -1) + addr += size + dma_mod; + + set_dma_start_addr(dma_ch, (unsigned long)addr); + set_dma_x_count(dma_ch, dma_count); + set_dma_x_modify(dma_ch, dma_mod); + set_dma_config(dma_ch, dma_config); + SSYNC(); + enable_dma(dma_ch); +} + +static int bfin_crc_run(struct bfin_crc *crc) +{ + int ret = 0; + unsigned long control; + unsigned int timeout = 100000; + int mod_dir = 1; + struct crc_info *info = crc->info; + + if (info->datasize == 0) + return 0; + else if (info->datasize % 4 != 0) { + dev_info(crc->mdev.this_device, "CRC data size should be multiply of 4 bytes.\n"); + return -EINVAL; + } + + /* config CRC */ + control = crc->opmode << OPMODE_OFFSET; + control |= (info->bitmirr << BITMIRR_OFFSET); + control |= (info->bytmirr << BYTMIRR_OFFSET); + control |= (info->w16swp << W16SWP_OFFSET); + control |= (info->fdsel << FDSEL_OFFSET); + control |= (info->rsltmirr << RSLTMIRR_OFFSET); + control |= (info->polymirr << POLYMIRR_OFFSET); + control |= (info->cmpmirr << CMPMIRR_OFFSET); + control |= AUTOCLRZ; + + if (crc->opmode == MODE_DMACPY_CRC) + control |= OBRSTALL; + + crc->regs->control = control; + SSYNC(); + + if (crc->opmode == MODE_CALC_CRC || crc->opmode == MODE_DMACPY_CRC) { + crc->regs->poly = info->crc_poly; + SSYNC(); + + while (!(crc->regs->status & LUTDONE) && (--timeout) > 0) + cpu_relax(); + + if (!timeout) { + dev_dbg(crc->mdev.this_device, "Timeout when generating LUT.\n"); + return -EBUSY; + } + } + + crc->regs->datacntrld = 0; + crc->regs->datacnt = info->datasize >> 2; + crc->regs->compare = info->crc_compare; + + /* setup CRC interrupts */ + crc->regs->status = CMPERRI | DCNTEXPI; + crc->regs->intrenset = CMPERRI | DCNTEXPI; + SSYNC(); + + /* setup CRC receive DMA */ + switch (crc->opmode) { + case MODE_DMACPY_CRC: + invalidate_dcache_range((unsigned long)info->out_addr, + (unsigned long)(info->out_addr + info->datasize)); + if (info->out_addr < info->in_addr + info->datasize) + mod_dir = -1; + bfin_crc_config_dma(crc->dma_ch_dest, info->out_addr, + info->datasize, WNR, mod_dir); + break; + case MODE_DATA_FILL: + crc->regs->fillval = info->val_fill; + invalidate_dcache_range((unsigned long)info->out_addr, + (unsigned long)(info->out_addr + info->datasize)); + bfin_crc_config_dma(crc->dma_ch_dest, info->out_addr, + info->datasize, WNR, 1); + break; + } + + /* enable CRC operation */ + crc->regs->control |= BLKEN; + SSYNC(); + + /* setup CRC transfer DMA */ + switch (crc->opmode) { + case MODE_CALC_CRC: + case MODE_DATA_VERIFY: + flush_dcache_range((unsigned long)info->in_addr, + (unsigned long)(info->in_addr + info->datasize)); + bfin_crc_config_dma(crc->dma_ch_src, info->in_addr, + info->datasize, 0, 1); + break; + case MODE_DMACPY_CRC: + flush_dcache_range((unsigned long)info->in_addr, + (unsigned long)(info->in_addr + info->datasize)); + bfin_crc_config_dma(crc->dma_ch_src, info->in_addr, + info->datasize, 0, mod_dir); + break; + } + + /* wait for completion */ + if ((ret = wait_for_completion_interruptible(&crc->c)) < 0) { + dev_dbg(crc->mdev.this_device, "Completion waiting is interrupted.\n"); + goto out; + } + if (crc->opmode == MODE_DMACPY_CRC || crc->opmode == MODE_DATA_FILL) + while (crc->regs->status & OBR) + cpu_relax(); + +out: + clear_dma_irqstat(crc->dma_ch_src); + clear_dma_irqstat(crc->dma_ch_dest); + disable_dma(crc->dma_ch_src); + disable_dma(crc->dma_ch_dest); + crc->regs->intrenclr = CMPERRI | DCNTEXPI; + crc->regs->status = CMPERRI | DCNTEXPI; + crc->regs->control = 0; + SSYNC(); + return ret; +} + +/** + * bfin_crc_handler - CRC interrupt handler + */ +static irqreturn_t bfin_crc_handler(int irq, void *dev_id) +{ + struct bfin_crc *crc = dev_id; + + if (crc->regs->status & DCNTEXP) { + crc->regs->status = DCNTEXP; + SSYNC(); + /* prepare results */ + switch (crc->opmode) { + case MODE_CALC_CRC: + case MODE_DMACPY_CRC: + crc->info->crc_result = crc->regs->result; + break; + case MODE_DATA_VERIFY: + crc->info->pos_verify = + (crc->regs->status & CMPERR) ? crc->regs->datacntcap : 0; + break; + }; + if (crc->regs->control | BLKEN) + complete(&crc->c); + + crc->regs->control &= ~BLKEN; + return IRQ_HANDLED; + } else + return IRQ_NONE; +} + +/** + * bfin_crc_open - Open the Device + * @inode: inode of device + * @filp: file handle of device + */ +static int bfin_crc_open(struct inode *inode, struct file *filp) +{ + int minor; + struct bfin_crc *crc; + int ret; + + minor = MINOR(inode->i_rdev); + filp->private_data = NULL; + list_for_each_entry(crc, &bfin_crc_list, list) + if (crc->mdev.minor == minor) { + filp->private_data = crc; + break; + } + + if (!filp->private_data) + return -ENODEV; + + if (mutex_lock_interruptible(&crc->mutex)) + return -ERESTARTSYS; + + init_completion(&crc->c); + + ret = request_irq(crc->irq, bfin_crc_handler, IRQF_SHARED, DRIVER_NAME, crc); + if (ret) { + pr_err("unable to request blackfin crc irq\n"); + goto out; + } + + if (request_dma(crc->dma_ch_src, "BFIN_CRC_SRC") < 0) { + printk(KERN_NOTICE "Unable to attach Blackfin CRC source DMA channel\n"); + ret = -EBUSY; + goto out; + } + + if (request_dma(crc->dma_ch_dest, "BFIN_CRC_DEST") < 0) { + printk(KERN_NOTICE "Unable to attach Blackfin CRC destination DMA channel\n"); + free_dma(crc->dma_ch_src); + ret = -EBUSY; + goto out; + } + +out: + mutex_unlock(&crc->mutex); + + return 0; +} + +/** + * bfin_crc_close - Close the Device + * @inode: inode of device + * @filp: file handle of device + */ +static int bfin_crc_release(struct inode *inode, struct file *filp) +{ + int minor; + struct bfin_crc *crc; + + minor = MINOR(inode->i_rdev); + filp->private_data = NULL; + list_for_each_entry(crc, &bfin_crc_list, list) + if (crc->mdev.minor == minor) { + filp->private_data = crc; + break; + } + + if (!filp->private_data) + return -ENODEV; + + if (mutex_lock_interruptible(&crc->mutex)) + return -ERESTARTSYS; + + free_dma(crc->dma_ch_src); + free_dma(crc->dma_ch_dest); + free_irq(crc->irq, crc); + mutex_unlock(&crc->mutex); + + return 0; +} + +/** + * bfin_crc_ioctl - Operate CRC Device + * @filp: file handle of device + * @cmd: hardware crc command + * @arg: argument + */ +static long bfin_crc_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct crc_info bfin_crc_info; + struct bfin_crc *crc; + int ret = 0; + + crc = filp->private_data; + + if (mutex_lock_interruptible(&crc->mutex)) + return -ERESTARTSYS; + + if (copy_from_user(&bfin_crc_info, argp, sizeof(bfin_crc_info))) { + ret = -EFAULT; + goto out; + } + + crc->info = &bfin_crc_info; + switch (cmd) { + case CRC_IOC_CALC_CRC: + crc->opmode = MODE_CALC_CRC; + break; + case CRC_IOC_MEMCPY_CRC: + crc->opmode = MODE_DMACPY_CRC; + break; + case CRC_IOC_VERIFY_VAL: + crc->opmode = MODE_DATA_VERIFY; + break; + case CRC_IOC_FILL_VAL: + crc->opmode = MODE_DATA_FILL; + break; + default: + ret = -ENOTTY; + goto out; + } + ret = bfin_crc_run(crc); + + if (ret >= 0 && copy_to_user(argp, &bfin_crc_info, sizeof(bfin_crc_info))) + ret = -EFAULT; + +out: + crc->info = NULL; + crc->opmode = 0; + mutex_unlock(&crc->mutex); + return ret; +} + +#ifdef CONFIG_PM +/** + * bfin_crc_suspend - suspend the watchdog + * @pdev: device being suspended + * @state: requested suspend state + */ +static int bfin_crc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct bfin_crc *crc = platform_get_drvdata(pdev); + int i = 100000; + + while ((crc->regs->control & BLKEN) && --i) + cpu_relax(); + + if (i == 0) + crc->regs->control &= ~BLKEN; + + return 0; +} + +/** + * bfin_crc_resume - resume the watchdog + * @pdev: device being resumed + */ +static int bfin_crc_resume(struct platform_device *pdev) +{ + return 0; +} +#else +# define bfin_crc_suspend NULL +# define bfin_crc_resume NULL +#endif + +static const struct file_operations bfin_crc_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = bfin_crc_ioctl, + .open = bfin_crc_open, + .release = bfin_crc_release, +}; + +/** + * bfin_crc_probe - Initialize module + * + * Registers the misc device. Actual device + * initialization is handled by bfin_crc_open(). + */ +static int __devinit bfin_crc_probe(struct platform_device *pdev) +{ + struct resource *res; + struct bfin_crc *crc = NULL; + int ret; + + crc = kzalloc(sizeof(*crc), GFP_KERNEL); + if (!crc) { + dev_err(&pdev->dev, "fail to malloc bfin_crc\n"); + return -ENOMEM; + } + + mutex_init(&crc->mutex); + crc->mdev.minor = MISC_DYNAMIC_MINOR; + snprintf(crc->name, 20, "%s%d", DRIVER_NAME, pdev->id); + crc->mdev.name = crc->name; + crc->mdev.fops = &bfin_crc_fops; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n"); + ret = -ENOENT; + goto out_error_free_mem; + } + + crc->regs = ioremap(res->start, resource_size(res)); + if (!crc->regs) { + dev_err(&pdev->dev, "Cannot map CRC IO\n"); + ret = -ENXIO; + goto out_error_free_mem; + } + + crc->irq = platform_get_irq(pdev, 0); + if (crc->irq < 0) { + dev_err(&pdev->dev, "No CRC DCNTEXP IRQ specified\n"); + ret = -ENOENT; + goto out_error_unmap; + } + + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No CRC DMA source channel specified\n"); + ret = -ENOENT; + goto out_error_unmap; + } + crc->dma_ch_src = res->start; + + res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (res == NULL) { + dev_err(&pdev->dev, "No CRC DMA destination channel specified\n"); + ret = -ENOENT; + goto out_error_unmap; + } + crc->dma_ch_dest = res->start; + + ret = misc_register(&crc->mdev); + if (ret) { + dev_err(&pdev->dev, "cannot register CRC miscdev\n"); + return ret; + } + + list_add(&crc->list, &bfin_crc_list); + dev_set_drvdata(&pdev->dev, crc); + + dev_info(&pdev->dev, "initialized\n"); + + return 0; + +out_error_unmap: + iounmap((void *)crc->regs); +out_error_free_mem: + kfree(crc); + return ret; +} + +/** + * bfin_crc_remove - Initialize module + * + * Unregisters the misc device. Actual device + * deinitialization is handled by bfin_crc_close(). + */ +static int __devexit bfin_crc_remove(struct platform_device *pdev) +{ + struct bfin_crc *crc = platform_get_drvdata(pdev); + + dev_set_drvdata(&pdev->dev, NULL); + + if (crc) { + misc_deregister(&crc->mdev); + list_del(&crc->list); + iounmap((void *)crc->regs); + kfree(crc); + } + + return 0; +} + +static struct platform_driver bfin_crc_driver = { + .probe = bfin_crc_probe, + .remove = __devexit_p(bfin_crc_remove), + .suspend = bfin_crc_suspend, + .resume = bfin_crc_resume, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +/** + * bfin_crc_init - Initialize module + * + * Checks the module params and registers the platform driver. + * Real work is in the platform probe function. + */ +static int __init bfin_crc_init(void) +{ + int ret; + + pr_info("Blackfin hardware CRC driver\n"); + + ret = platform_driver_register(&bfin_crc_driver); + if (ret) { + pr_info(KERN_ERR "unable to register driver\n"); + return ret; + } + + return 0; +} + +/** + * bfin_crc_exit - Deinitialize module + */ +static void __exit bfin_crc_exit(void) +{ + platform_driver_unregister(&bfin_crc_driver); +} + +module_init(bfin_crc_init); +module_exit(bfin_crc_exit); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("Blackfin hardware CRC Driver"); +MODULE_LICENSE("GPL"); -- 1.7.0.4 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/