diff options
Diffstat (limited to 'drivers/staging/goldfish')
-rw-r--r-- | drivers/staging/goldfish/Kconfig | 13 | ||||
-rw-r--r-- | drivers/staging/goldfish/Makefile | 6 | ||||
-rw-r--r-- | drivers/staging/goldfish/README | 11 | ||||
-rw-r--r-- | drivers/staging/goldfish/goldfish_audio.c | 355 | ||||
-rw-r--r-- | drivers/staging/goldfish/goldfish_nand.c | 442 | ||||
-rw-r--r-- | drivers/staging/goldfish/goldfish_nand_reg.h | 76 |
6 files changed, 903 insertions, 0 deletions
diff --git a/drivers/staging/goldfish/Kconfig b/drivers/staging/goldfish/Kconfig new file mode 100644 index 000000000..4e0946024 --- /dev/null +++ b/drivers/staging/goldfish/Kconfig @@ -0,0 +1,13 @@ +config GOLDFISH_AUDIO + tristate "Goldfish AVD Audio Device" + depends on GOLDFISH + ---help--- + Emulated audio channel for the Goldfish Android Virtual Device + +config MTD_GOLDFISH_NAND + tristate "Goldfish NAND device" + depends on GOLDFISH + depends on MTD + help + Drives the emulated NAND flash device on the Google Goldfish + Android virtual device. diff --git a/drivers/staging/goldfish/Makefile b/drivers/staging/goldfish/Makefile new file mode 100644 index 000000000..dec34ad58 --- /dev/null +++ b/drivers/staging/goldfish/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the Goldfish audio driver +# + +obj-$(CONFIG_GOLDFISH_AUDIO) += goldfish_audio.o +obj-$(CONFIG_MTD_GOLDFISH_NAND) += goldfish_nand.o diff --git a/drivers/staging/goldfish/README b/drivers/staging/goldfish/README new file mode 100644 index 000000000..183af0053 --- /dev/null +++ b/drivers/staging/goldfish/README @@ -0,0 +1,11 @@ +Audio +----- +- Move to using the ALSA framework not faking it +- Fix the wrong user page DMA (moving to ALSA may fix that too) + +NAND +---- +- Remove excess checking of parameters in calls +- Use dma coherent memory not kmalloc/__pa for the memory (this is just + a cleanliness issue not a correctness one) + diff --git a/drivers/staging/goldfish/goldfish_audio.c b/drivers/staging/goldfish/goldfish_audio.c new file mode 100644 index 000000000..702ae04df --- /dev/null +++ b/drivers/staging/goldfish/goldfish_audio.c @@ -0,0 +1,355 @@ +/* + * drivers/misc/goldfish_audio.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (C) 2012 Intel, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/module.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/sched.h> +#include <linux/dma-mapping.h> +#include <linux/uaccess.h> +#include <linux/goldfish.h> + +MODULE_AUTHOR("Google, Inc."); +MODULE_DESCRIPTION("Android QEMU Audio Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +struct goldfish_audio { + char __iomem *reg_base; + int irq; + /* lock protects access to buffer_status and to device registers */ + spinlock_t lock; + wait_queue_head_t wait; + + char *buffer_virt; /* combined buffer virtual address */ + unsigned long buffer_phys; /* combined buffer physical address */ + + char *write_buffer1; /* write buffer 1 virtual address */ + char *write_buffer2; /* write buffer 2 virtual address */ + char *read_buffer; /* read buffer virtual address */ + int buffer_status; + int read_supported; /* true if we have audio input support */ +}; + +/* + * We will allocate two read buffers and two write buffers. + * Having two read buffers facilitate stereo -> mono conversion. + * Having two write buffers facilitate interleaved IO. + */ +#define READ_BUFFER_SIZE 16384 +#define WRITE_BUFFER_SIZE 16384 +#define COMBINED_BUFFER_SIZE ((2 * READ_BUFFER_SIZE) + \ + (2 * WRITE_BUFFER_SIZE)) + +#define AUDIO_READ(data, addr) (readl(data->reg_base + addr)) +#define AUDIO_WRITE(data, addr, x) (writel(x, data->reg_base + addr)) +#define AUDIO_WRITE64(data, addr, addr2, x) \ + (gf_write64((u64)(x), data->reg_base + addr, data->reg_base+addr2)) + +/* + * temporary variable used between goldfish_audio_probe() and + * goldfish_audio_open() + */ +static struct goldfish_audio *audio_data; + +enum { + /* audio status register */ + AUDIO_INT_STATUS = 0x00, + /* set this to enable IRQ */ + AUDIO_INT_ENABLE = 0x04, + /* set these to specify buffer addresses */ + AUDIO_SET_WRITE_BUFFER_1 = 0x08, + AUDIO_SET_WRITE_BUFFER_2 = 0x0C, + /* set number of bytes in buffer to write */ + AUDIO_WRITE_BUFFER_1 = 0x10, + AUDIO_WRITE_BUFFER_2 = 0x14, + AUDIO_SET_WRITE_BUFFER_1_HIGH = 0x28, + AUDIO_SET_WRITE_BUFFER_2_HIGH = 0x30, + + /* true if audio input is supported */ + AUDIO_READ_SUPPORTED = 0x18, + /* buffer to use for audio input */ + AUDIO_SET_READ_BUFFER = 0x1C, + AUDIO_SET_READ_BUFFER_HIGH = 0x34, + + /* driver writes number of bytes to read */ + AUDIO_START_READ = 0x20, + + /* number of bytes available in read buffer */ + AUDIO_READ_BUFFER_AVAILABLE = 0x24, + + /* AUDIO_INT_STATUS bits */ + + /* this bit set when it is safe to write more bytes to the buffer */ + AUDIO_INT_WRITE_BUFFER_1_EMPTY = 1U << 0, + AUDIO_INT_WRITE_BUFFER_2_EMPTY = 1U << 1, + AUDIO_INT_READ_BUFFER_FULL = 1U << 2, + + AUDIO_INT_MASK = AUDIO_INT_WRITE_BUFFER_1_EMPTY | + AUDIO_INT_WRITE_BUFFER_2_EMPTY | + AUDIO_INT_READ_BUFFER_FULL, +}; + +static atomic_t open_count = ATOMIC_INIT(0); + +static ssize_t goldfish_audio_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct goldfish_audio *data = fp->private_data; + int length; + int result = 0; + + if (!data->read_supported) + return -ENODEV; + + while (count > 0) { + length = (count > READ_BUFFER_SIZE ? READ_BUFFER_SIZE : count); + AUDIO_WRITE(data, AUDIO_START_READ, length); + + wait_event_interruptible(data->wait, data->buffer_status & + AUDIO_INT_READ_BUFFER_FULL); + + length = AUDIO_READ(data, AUDIO_READ_BUFFER_AVAILABLE); + + /* copy data to user space */ + if (copy_to_user(buf, data->read_buffer, length)) + return -EFAULT; + + result += length; + buf += length; + count -= length; + } + return result; +} + +static ssize_t goldfish_audio_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct goldfish_audio *data = fp->private_data; + unsigned long irq_flags; + ssize_t result = 0; + char *kbuf; + + while (count > 0) { + ssize_t copy = count; + + if (copy > WRITE_BUFFER_SIZE) + copy = WRITE_BUFFER_SIZE; + wait_event_interruptible(data->wait, data->buffer_status & + (AUDIO_INT_WRITE_BUFFER_1_EMPTY | + AUDIO_INT_WRITE_BUFFER_2_EMPTY)); + + if ((data->buffer_status & AUDIO_INT_WRITE_BUFFER_1_EMPTY) != 0) + kbuf = data->write_buffer1; + else + kbuf = data->write_buffer2; + + /* copy from user space to the appropriate buffer */ + if (copy_from_user(kbuf, buf, copy)) { + result = -EFAULT; + break; + } + + spin_lock_irqsave(&data->lock, irq_flags); + /* + * clear the buffer empty flag, and signal the emulator + * to start writing the buffer + */ + if (kbuf == data->write_buffer1) { + data->buffer_status &= ~AUDIO_INT_WRITE_BUFFER_1_EMPTY; + AUDIO_WRITE(data, AUDIO_WRITE_BUFFER_1, copy); + } else { + data->buffer_status &= ~AUDIO_INT_WRITE_BUFFER_2_EMPTY; + AUDIO_WRITE(data, AUDIO_WRITE_BUFFER_2, copy); + } + spin_unlock_irqrestore(&data->lock, irq_flags); + + buf += copy; + result += copy; + count -= copy; + } + return result; +} + +static int goldfish_audio_open(struct inode *ip, struct file *fp) +{ + if (!audio_data) + return -ENODEV; + + if (atomic_inc_return(&open_count) == 1) { + fp->private_data = audio_data; + audio_data->buffer_status = (AUDIO_INT_WRITE_BUFFER_1_EMPTY | + AUDIO_INT_WRITE_BUFFER_2_EMPTY); + AUDIO_WRITE(audio_data, AUDIO_INT_ENABLE, AUDIO_INT_MASK); + return 0; + } + + atomic_dec(&open_count); + return -EBUSY; +} + +static int goldfish_audio_release(struct inode *ip, struct file *fp) +{ + atomic_dec(&open_count); + /* FIXME: surely this is wrong for the multi-opened case */ + AUDIO_WRITE(audio_data, AUDIO_INT_ENABLE, 0); + return 0; +} + +static long goldfish_audio_ioctl(struct file *fp, unsigned int cmd, + unsigned long arg) +{ + /* temporary workaround, until we switch to the ALSA API */ + if (cmd == 315) + return -1; + + return 0; +} + +static irqreturn_t goldfish_audio_interrupt(int irq, void *dev_id) +{ + unsigned long irq_flags; + struct goldfish_audio *data = dev_id; + u32 status; + + spin_lock_irqsave(&data->lock, irq_flags); + + /* read buffer status flags */ + status = AUDIO_READ(data, AUDIO_INT_STATUS); + status &= AUDIO_INT_MASK; + /* + * if buffers are newly empty, wake up blocked + * goldfish_audio_write() call + */ + if (status) { + data->buffer_status = status; + wake_up(&data->wait); + } + + spin_unlock_irqrestore(&data->lock, irq_flags); + return status ? IRQ_HANDLED : IRQ_NONE; +} + +/* file operations for /dev/eac */ +static const struct file_operations goldfish_audio_fops = { + .owner = THIS_MODULE, + .read = goldfish_audio_read, + .write = goldfish_audio_write, + .open = goldfish_audio_open, + .release = goldfish_audio_release, + .unlocked_ioctl = goldfish_audio_ioctl, +}; + +static struct miscdevice goldfish_audio_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "eac", + .fops = &goldfish_audio_fops, +}; + +static int goldfish_audio_probe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + struct goldfish_audio *data; + dma_addr_t buf_addr; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + spin_lock_init(&data->lock); + init_waitqueue_head(&data->wait); + platform_set_drvdata(pdev, data); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "platform_get_resource failed\n"); + return -ENODEV; + } + data->reg_base = devm_ioremap(&pdev->dev, r->start, PAGE_SIZE); + if (data->reg_base == NULL) + return -ENOMEM; + + data->irq = platform_get_irq(pdev, 0); + if (data->irq < 0) { + dev_err(&pdev->dev, "platform_get_irq failed\n"); + return -ENODEV; + } + data->buffer_virt = dmam_alloc_coherent(&pdev->dev, + COMBINED_BUFFER_SIZE, &buf_addr, GFP_KERNEL); + if (data->buffer_virt == NULL) { + dev_err(&pdev->dev, "allocate buffer failed\n"); + return -ENOMEM; + } + data->buffer_phys = buf_addr; + data->write_buffer1 = data->buffer_virt; + data->write_buffer2 = data->buffer_virt + WRITE_BUFFER_SIZE; + data->read_buffer = data->buffer_virt + 2 * WRITE_BUFFER_SIZE; + + ret = devm_request_irq(&pdev->dev, data->irq, goldfish_audio_interrupt, + IRQF_SHARED, pdev->name, data); + if (ret) { + dev_err(&pdev->dev, "request_irq failed\n"); + return ret; + } + + ret = misc_register(&goldfish_audio_device); + if (ret) { + dev_err(&pdev->dev, + "misc_register returned %d in goldfish_audio_init\n", + ret); + return ret; + } + + AUDIO_WRITE64(data, AUDIO_SET_WRITE_BUFFER_1, + AUDIO_SET_WRITE_BUFFER_1_HIGH, buf_addr); + buf_addr += WRITE_BUFFER_SIZE; + + AUDIO_WRITE64(data, AUDIO_SET_WRITE_BUFFER_2, + AUDIO_SET_WRITE_BUFFER_2_HIGH, buf_addr); + + buf_addr += WRITE_BUFFER_SIZE; + + data->read_supported = AUDIO_READ(data, AUDIO_READ_SUPPORTED); + if (data->read_supported) + AUDIO_WRITE64(data, AUDIO_SET_READ_BUFFER, + AUDIO_SET_READ_BUFFER_HIGH, buf_addr); + + audio_data = data; + return 0; +} + +static int goldfish_audio_remove(struct platform_device *pdev) +{ + misc_deregister(&goldfish_audio_device); + audio_data = NULL; + return 0; +} + +static struct platform_driver goldfish_audio_driver = { + .probe = goldfish_audio_probe, + .remove = goldfish_audio_remove, + .driver = { + .name = "goldfish_audio" + } +}; + +module_platform_driver(goldfish_audio_driver); diff --git a/drivers/staging/goldfish/goldfish_nand.c b/drivers/staging/goldfish/goldfish_nand.c new file mode 100644 index 000000000..213877a2c --- /dev/null +++ b/drivers/staging/goldfish/goldfish_nand.c @@ -0,0 +1,442 @@ +/* + * drivers/mtd/devices/goldfish_nand.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (C) 2012 Intel, Inc. + * Copyright (C) 2013 Intel, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/io.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/vmalloc.h> +#include <linux/mtd/mtd.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/goldfish.h> +#include <asm/div64.h> + +#include "goldfish_nand_reg.h" + +struct goldfish_nand { + /* lock protects access to the device registers */ + struct mutex lock; + unsigned char __iomem *base; + struct cmd_params *cmd_params; + size_t mtd_count; + struct mtd_info mtd[0]; +}; + +static u32 goldfish_nand_cmd_with_params(struct mtd_info *mtd, + enum nand_cmd cmd, u64 addr, u32 len, + void *ptr, u32 *rv) +{ + u32 cmdp; + struct goldfish_nand *nand = mtd->priv; + struct cmd_params *cps = nand->cmd_params; + unsigned char __iomem *base = nand->base; + + if (cps == NULL) + return -1; + + switch (cmd) { + case NAND_CMD_ERASE: + cmdp = NAND_CMD_ERASE_WITH_PARAMS; + break; + case NAND_CMD_READ: + cmdp = NAND_CMD_READ_WITH_PARAMS; + break; + case NAND_CMD_WRITE: + cmdp = NAND_CMD_WRITE_WITH_PARAMS; + break; + default: + return -1; + } + cps->dev = mtd - nand->mtd; + cps->addr_high = (u32)(addr >> 32); + cps->addr_low = (u32)addr; + cps->transfer_size = len; + cps->data = (unsigned long)ptr; + writel(cmdp, base + NAND_COMMAND); + *rv = cps->result; + return 0; +} + +static u32 goldfish_nand_cmd(struct mtd_info *mtd, enum nand_cmd cmd, + u64 addr, u32 len, void *ptr) +{ + struct goldfish_nand *nand = mtd->priv; + u32 rv; + unsigned char __iomem *base = nand->base; + + mutex_lock(&nand->lock); + if (goldfish_nand_cmd_with_params(mtd, cmd, addr, len, ptr, &rv)) { + writel(mtd - nand->mtd, base + NAND_DEV); + writel((u32)(addr >> 32), base + NAND_ADDR_HIGH); + writel((u32)addr, base + NAND_ADDR_LOW); + writel(len, base + NAND_TRANSFER_SIZE); + gf_write64((u64)ptr, base + NAND_DATA, base + NAND_DATA_HIGH); + writel(cmd, base + NAND_COMMAND); + rv = readl(base + NAND_RESULT); + } + mutex_unlock(&nand->lock); + return rv; +} + +static int goldfish_nand_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + loff_t ofs = instr->addr; + u32 len = instr->len; + u32 rem; + + if (ofs + len > mtd->size) + goto invalid_arg; + rem = do_div(ofs, mtd->writesize); + if (rem) + goto invalid_arg; + ofs *= (mtd->writesize + mtd->oobsize); + + if (len % mtd->writesize) + goto invalid_arg; + len = len / mtd->writesize * (mtd->writesize + mtd->oobsize); + + if (goldfish_nand_cmd(mtd, NAND_CMD_ERASE, ofs, len, NULL) != len) { + pr_err("goldfish_nand_erase: erase failed, start %llx, len %x, dev_size %llx, erase_size %x\n", + ofs, len, mtd->size, mtd->erasesize); + return -EIO; + } + + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + + return 0; + +invalid_arg: + pr_err("goldfish_nand_erase: invalid erase, start %llx, len %x, dev_size %llx, erase_size %x\n", + ofs, len, mtd->size, mtd->erasesize); + return -EINVAL; +} + +static int goldfish_nand_read_oob(struct mtd_info *mtd, loff_t ofs, + struct mtd_oob_ops *ops) +{ + u32 rem; + + if (ofs + ops->len > mtd->size) + goto invalid_arg; + if (ops->datbuf && ops->len && ops->len != mtd->writesize) + goto invalid_arg; + if (ops->ooblen + ops->ooboffs > mtd->oobsize) + goto invalid_arg; + + rem = do_div(ofs, mtd->writesize); + if (rem) + goto invalid_arg; + ofs *= (mtd->writesize + mtd->oobsize); + + if (ops->datbuf) + ops->retlen = goldfish_nand_cmd(mtd, NAND_CMD_READ, ofs, + ops->len, ops->datbuf); + ofs += mtd->writesize + ops->ooboffs; + if (ops->oobbuf) + ops->oobretlen = goldfish_nand_cmd(mtd, NAND_CMD_READ, ofs, + ops->ooblen, ops->oobbuf); + return 0; + +invalid_arg: + pr_err("goldfish_nand_read_oob: invalid read, start %llx, len %zx, ooblen %zx, dev_size %llx, write_size %x\n", + ofs, ops->len, ops->ooblen, mtd->size, mtd->writesize); + return -EINVAL; +} + +static int goldfish_nand_write_oob(struct mtd_info *mtd, loff_t ofs, + struct mtd_oob_ops *ops) +{ + u32 rem; + + if (ofs + ops->len > mtd->size) + goto invalid_arg; + if (ops->len && ops->len != mtd->writesize) + goto invalid_arg; + if (ops->ooblen + ops->ooboffs > mtd->oobsize) + goto invalid_arg; + + rem = do_div(ofs, mtd->writesize); + if (rem) + goto invalid_arg; + ofs *= (mtd->writesize + mtd->oobsize); + + if (ops->datbuf) + ops->retlen = goldfish_nand_cmd(mtd, NAND_CMD_WRITE, ofs, + ops->len, ops->datbuf); + ofs += mtd->writesize + ops->ooboffs; + if (ops->oobbuf) + ops->oobretlen = goldfish_nand_cmd(mtd, NAND_CMD_WRITE, ofs, + ops->ooblen, ops->oobbuf); + return 0; + +invalid_arg: + pr_err("goldfish_nand_write_oob: invalid write, start %llx, len %zx, ooblen %zx, dev_size %llx, write_size %x\n", + ofs, ops->len, ops->ooblen, mtd->size, mtd->writesize); + return -EINVAL; +} + +static int goldfish_nand_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + u32 rem; + + if (from + len > mtd->size) + goto invalid_arg; + + rem = do_div(from, mtd->writesize); + if (rem) + goto invalid_arg; + from *= (mtd->writesize + mtd->oobsize); + + *retlen = goldfish_nand_cmd(mtd, NAND_CMD_READ, from, len, buf); + return 0; + +invalid_arg: + pr_err("goldfish_nand_read: invalid read, start %llx, len %zx, dev_size %llx, write_size %x\n", + from, len, mtd->size, mtd->writesize); + return -EINVAL; +} + +static int goldfish_nand_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + u32 rem; + + if (to + len > mtd->size) + goto invalid_arg; + + rem = do_div(to, mtd->writesize); + if (rem) + goto invalid_arg; + to *= (mtd->writesize + mtd->oobsize); + + *retlen = goldfish_nand_cmd(mtd, NAND_CMD_WRITE, to, len, (void *)buf); + return 0; + +invalid_arg: + pr_err("goldfish_nand_write: invalid write, start %llx, len %zx, dev_size %llx, write_size %x\n", + to, len, mtd->size, mtd->writesize); + return -EINVAL; +} + +static int goldfish_nand_block_isbad(struct mtd_info *mtd, loff_t ofs) +{ + u32 rem; + + if (ofs >= mtd->size) + goto invalid_arg; + + rem = do_div(ofs, mtd->erasesize); + if (rem) + goto invalid_arg; + ofs *= mtd->erasesize / mtd->writesize; + ofs *= (mtd->writesize + mtd->oobsize); + + return goldfish_nand_cmd(mtd, NAND_CMD_BLOCK_BAD_GET, ofs, 0, NULL); + +invalid_arg: + pr_err("goldfish_nand_block_isbad: invalid arg, ofs %llx, dev_size %llx, write_size %x\n", + ofs, mtd->size, mtd->writesize); + return -EINVAL; +} + +static int goldfish_nand_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + u32 rem; + + if (ofs >= mtd->size) + goto invalid_arg; + + rem = do_div(ofs, mtd->erasesize); + if (rem) + goto invalid_arg; + ofs *= mtd->erasesize / mtd->writesize; + ofs *= (mtd->writesize + mtd->oobsize); + + if (goldfish_nand_cmd(mtd, NAND_CMD_BLOCK_BAD_SET, ofs, 0, NULL) != 1) + return -EIO; + return 0; + +invalid_arg: + pr_err("goldfish_nand_block_markbad: invalid arg, ofs %llx, dev_size %llx, write_size %x\n", + ofs, mtd->size, mtd->writesize); + return -EINVAL; +} + +static int nand_setup_cmd_params(struct platform_device *pdev, + struct goldfish_nand *nand) +{ + u64 paddr; + unsigned char __iomem *base = nand->base; + + nand->cmd_params = devm_kzalloc(&pdev->dev, + sizeof(struct cmd_params), GFP_KERNEL); + if (!nand->cmd_params) + return -1; + + paddr = __pa(nand->cmd_params); + writel((u32)(paddr >> 32), base + NAND_CMD_PARAMS_ADDR_HIGH); + writel((u32)paddr, base + NAND_CMD_PARAMS_ADDR_LOW); + return 0; +} + +static int goldfish_nand_init_device(struct platform_device *pdev, + struct goldfish_nand *nand, int id) +{ + u32 name_len; + u32 result; + u32 flags; + unsigned char __iomem *base = nand->base; + struct mtd_info *mtd = &nand->mtd[id]; + char *name; + + mutex_lock(&nand->lock); + writel(id, base + NAND_DEV); + flags = readl(base + NAND_DEV_FLAGS); + name_len = readl(base + NAND_DEV_NAME_LEN); + mtd->writesize = readl(base + NAND_DEV_PAGE_SIZE); + mtd->size = readl(base + NAND_DEV_SIZE_LOW); + mtd->size |= (u64)readl(base + NAND_DEV_SIZE_HIGH) << 32; + mtd->oobsize = readl(base + NAND_DEV_EXTRA_SIZE); + mtd->oobavail = mtd->oobsize; + mtd->erasesize = readl(base + NAND_DEV_ERASE_SIZE) / + (mtd->writesize + mtd->oobsize) * mtd->writesize; + do_div(mtd->size, mtd->writesize + mtd->oobsize); + mtd->size *= mtd->writesize; + dev_dbg(&pdev->dev, + "goldfish nand dev%d: size %llx, page %d, extra %d, erase %d\n", + id, mtd->size, mtd->writesize, + mtd->oobsize, mtd->erasesize); + mutex_unlock(&nand->lock); + + mtd->priv = nand; + + name = devm_kzalloc(&pdev->dev, name_len + 1, GFP_KERNEL); + if (!name) + return -ENOMEM; + mtd->name = name; + + result = goldfish_nand_cmd(mtd, NAND_CMD_GET_DEV_NAME, 0, name_len, + name); + if (result != name_len) { + dev_err(&pdev->dev, + "goldfish_nand_init_device failed to get dev name %d != %d\n", + result, name_len); + return -ENODEV; + } + ((char *)mtd->name)[name_len] = '\0'; + + /* Setup the MTD structure */ + mtd->type = MTD_NANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + if (flags & NAND_DEV_FLAG_READ_ONLY) + mtd->flags &= ~MTD_WRITEABLE; + if (flags & NAND_DEV_FLAG_CMD_PARAMS_CAP) + nand_setup_cmd_params(pdev, nand); + + mtd->owner = THIS_MODULE; + mtd->_erase = goldfish_nand_erase; + mtd->_read = goldfish_nand_read; + mtd->_write = goldfish_nand_write; + mtd->_read_oob = goldfish_nand_read_oob; + mtd->_write_oob = goldfish_nand_write_oob; + mtd->_block_isbad = goldfish_nand_block_isbad; + mtd->_block_markbad = goldfish_nand_block_markbad; + + if (mtd_device_register(mtd, NULL, 0)) + return -EIO; + + return 0; +} + +static int goldfish_nand_probe(struct platform_device *pdev) +{ + u32 num_dev; + int i; + int err; + u32 num_dev_working; + u32 version; + struct resource *r; + struct goldfish_nand *nand; + unsigned char __iomem *base; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) + return -ENODEV; + + base = devm_ioremap(&pdev->dev, r->start, PAGE_SIZE); + if (!base) + return -ENOMEM; + + version = readl(base + NAND_VERSION); + if (version != NAND_VERSION_CURRENT) { + dev_err(&pdev->dev, + "goldfish_nand_init: version mismatch, got %d, expected %d\n", + version, NAND_VERSION_CURRENT); + return -ENODEV; + } + num_dev = readl(base + NAND_NUM_DEV); + if (num_dev == 0) + return -ENODEV; + + nand = devm_kzalloc(&pdev->dev, sizeof(*nand) + + sizeof(struct mtd_info) * num_dev, GFP_KERNEL); + if (!nand) + return -ENOMEM; + + mutex_init(&nand->lock); + nand->base = base; + nand->mtd_count = num_dev; + platform_set_drvdata(pdev, nand); + + num_dev_working = 0; + for (i = 0; i < num_dev; i++) { + err = goldfish_nand_init_device(pdev, nand, i); + if (err == 0) + num_dev_working++; + } + if (num_dev_working == 0) + return -ENODEV; + return 0; +} + +static int goldfish_nand_remove(struct platform_device *pdev) +{ + struct goldfish_nand *nand = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < nand->mtd_count; i++) { + if (nand->mtd[i].name) + mtd_device_unregister(&nand->mtd[i]); + } + return 0; +} + +static struct platform_driver goldfish_nand_driver = { + .probe = goldfish_nand_probe, + .remove = goldfish_nand_remove, + .driver = { + .name = "goldfish_nand" + } +}; + +module_platform_driver(goldfish_nand_driver); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/goldfish/goldfish_nand_reg.h b/drivers/staging/goldfish/goldfish_nand_reg.h new file mode 100644 index 000000000..fe7f47c7a --- /dev/null +++ b/drivers/staging/goldfish/goldfish_nand_reg.h @@ -0,0 +1,76 @@ +/* + * drivers/mtd/devices/goldfish_nand_reg.h + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef GOLDFISH_NAND_REG_H +#define GOLDFISH_NAND_REG_H + +enum nand_cmd { + /* Write device name for NAND_DEV to NAND_DATA (vaddr) */ + NAND_CMD_GET_DEV_NAME, + NAND_CMD_READ, + NAND_CMD_WRITE, + NAND_CMD_ERASE, + /* NAND_RESULT is 1 if block is bad, 0 if it is not */ + NAND_CMD_BLOCK_BAD_GET, + NAND_CMD_BLOCK_BAD_SET, + NAND_CMD_READ_WITH_PARAMS, + NAND_CMD_WRITE_WITH_PARAMS, + NAND_CMD_ERASE_WITH_PARAMS +}; + +enum nand_dev_flags { + NAND_DEV_FLAG_READ_ONLY = 0x00000001, + NAND_DEV_FLAG_CMD_PARAMS_CAP = 0x00000002, +}; + +#define NAND_VERSION_CURRENT (1) + +enum nand_reg { + /* Global */ + NAND_VERSION = 0x000, + NAND_NUM_DEV = 0x004, + NAND_DEV = 0x008, + + /* Dev info */ + NAND_DEV_FLAGS = 0x010, + NAND_DEV_NAME_LEN = 0x014, + NAND_DEV_PAGE_SIZE = 0x018, + NAND_DEV_EXTRA_SIZE = 0x01c, + NAND_DEV_ERASE_SIZE = 0x020, + NAND_DEV_SIZE_LOW = 0x028, + NAND_DEV_SIZE_HIGH = 0x02c, + + /* Command */ + NAND_RESULT = 0x040, + NAND_COMMAND = 0x044, + NAND_DATA = 0x048, + NAND_DATA_HIGH = 0x100, + NAND_TRANSFER_SIZE = 0x04c, + NAND_ADDR_LOW = 0x050, + NAND_ADDR_HIGH = 0x054, + NAND_CMD_PARAMS_ADDR_LOW = 0x058, + NAND_CMD_PARAMS_ADDR_HIGH = 0x05c, +}; + +struct cmd_params { + uint32_t dev; + uint32_t addr_low; + uint32_t addr_high; + uint32_t transfer_size; + unsigned long data; + uint32_t result; +}; +#endif |