/*
* Cryptographic API.
*
* Support for OMAP AES HW acceleration.
*
* Copyright (c) 2010 Nokia Corporation
* Author: Dmitry Kasatkin <dmitry.kasatkin@nokia.com>
*
* 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.
*
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/err.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/scatterlist.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/crypto.h>
#include <linux/interrupt.h>
#include <crypto/scatterwalk.h>
#include <crypto/aes.h>
#include <plat/cpu.h>
#include <plat/dma.h>
/* OMAP TRM gives bitfields as start:end, where start is the higher bit
number. For example 7:0 */
#define FLD_MASK(start, end) (((1 << ((start) - (end) + 1)) - 1) << (end))
#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end))
#define AES_REG_KEY(x) (0x1C - ((x ^ 0x01) * 0x04))
#define AES_REG_IV(x) (0x20 + ((x) * 0x04))
#define AES_REG_CTRL 0x30
#define AES_REG_CTRL_CTR_WIDTH (1 << 7)
#define AES_REG_CTRL_CTR (1 << 6)
#define AES_REG_CTRL_CBC (1 << 5)
#define AES_REG_CTRL_KEY_SIZE (3 << 3)
#define AES_REG_CTRL_DIRECTION (1 << 2)
#define AES_REG_CTRL_INPUT_READY (1 << 1)
#define AES_REG_CTRL_OUTPUT_READY (1 << 0)
#define AES_REG_DATA 0x34
#define AES_REG_DATA_N(x) (0x34 + ((x) * 0x04))
#define AES_REG_REV 0x44
#define AES_REG_REV_MAJOR 0xF0
#define AES_REG_REV_MINOR 0x0F
#define AES_REG_MASK 0x48
#define AES_REG_MASK_SIDLE (1 << 6)
#define AES_REG_MASK_START (1 << 5)
#define AES_REG_MASK_DMA_OUT_EN (1 << 3)
#define AES_REG_MASK_DMA_IN_EN (1 << 2)
#define AES_REG_MASK_SOFTRESET (1 << 1)
#define AES_REG_AUTOIDLE (1 << 0)
#define AES_REG_SYSSTATUS 0x4C
#define AES_REG_SYSSTATUS_RESETDONE (1 << 0)
#define DEFAULT_TIMEOUT (5*HZ)
#define FLAGS_MODE_MASK 0x000f
#define FLAGS_ENCRYPT BIT(0)
#define FLAGS_CBC BIT(1)
#define FLAGS_GIV BIT(2)
#define FLAGS_NEW_KEY BIT(4)
#define FLAGS_NEW_IV BIT(5)
#define FLAGS_INIT BIT(6)
#define FLAGS_FAST BIT(7)
#define FLAGS_BUSY BIT(8)
struct omap_aes_ctx {
struct omap_aes_dev *dd;
int keylen;
u32 key[AES_KEYSIZE_256 / sizeof(u32)];
unsigned long flags;
};
struct omap_aes_reqctx {
unsigned long mode;
};
#define OMAP_AES_QUEUE_LENGTH 1
#define OMAP_AES_CACHE_SIZE 0
struct omap_aes_dev {
struct list_head list;
unsigned long phys_base;
void __iomem *io_base;
struct clk *iclk;
struct omap_aes_ctx *ctx;
struct device *dev;
unsigned long flags;
int err;
u32 *iv;
u32 ctrl;
spinlock_t lock;
struct crypto_queue queue;
struct tasklet_struct done_task;
struct tasklet_struct queue_task;
struct ablkcipher_request *req;
size_t total;
struct scatterlist *in_sg;
size_t in_offset;
struct scatterlist *out_sg;
size_t out_offset;
size_t buflen;
void *buf_in;
size_t dma_size;
int dma_in;
int dma_lch_in;
dma_addr_t dma_addr_in;
void *buf_out;
int dma_out;
int dma_lch_out;
dma_addr_t dma_addr_out;
};
/* keep registered devices data here */
static LIST_HEAD(dev_list);
static DEFINE_SPINLOCK(list_lock);
static inline u32 omap_aes_read(struct omap_aes_dev *dd, u32 offset)
{
return __raw_readl(dd->io_base + offset);
}
static inline void omap_aes_write(struct omap_aes_dev *dd, u32 offset,
u32 value)
{
__raw_writel(value, dd->io_base + offset);
}
static inline void omap_aes_write_mask(struct omap_aes_dev *dd, u32 offset,
u32 value, u32 mask)
{
u32 val;
val = omap_aes_read(dd, offset);
val &= ~mask;
val |= value;
omap_aes_write(dd, offset, val);
}
static void omap_aes_write_n(struct omap_aes_dev *dd, u32 offset,
u32 *value, int count)
{
for (; count--; value++, offset += 4)
omap_aes_write(dd, offset, *value);
}
static int omap_aes_wait(struct omap_aes_dev *dd, u32 offset, u32 bit)
{
unsigned long timeout = jiffies + DEFAULT_TIMEOUT;
while (!(omap_aes_read(dd, offset) & bit)) {
if (time_is_before_jiffies(timeout)) {
dev_err(dd->dev, "omap-aes timeout\n");
return -ETIMEDOUT;
}
}
return 0;
}
static int omap_aes_hw_init(struct omap_aes_dev *dd)
{
clk_enable(dd->iclk);
if (!(dd->flags & FLAGS_INIT)) {
/* is it necessary to reset before every operation? */
omap_aes_write_mask(dd, AES_REG_MASK, AES_REG_MASK_SOFTRESET,
AES_REG_MASK_SOFTRESET);
/*
* prevent OCP bus error (SRESP) in case an access to the module
* is performed while the module is coming out of soft reset
*/
__asm__ __volatile__("nop");
__asm__ __volatile__("nop");
if (omap_aes_wait(dd, AES_REG_SYSSTATUS,
AES_REG_SYSSTATUS_RESETDONE)) {
clk_disable(dd->iclk);
return -ETIMEDOUT;
}
dd->flags |= FLAGS_INIT;
dd->err