aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/char/broadcom/bcm2835_smi_dev.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/broadcom/bcm2835_smi_dev.c')
-rw-r--r--drivers/char/broadcom/bcm2835_smi_dev.c402
1 files changed, 402 insertions, 0 deletions
diff --git a/drivers/char/broadcom/bcm2835_smi_dev.c b/drivers/char/broadcom/bcm2835_smi_dev.c
new file mode 100644
index 000000000000..9db8f1e3db0f
--- /dev/null
+++ b/drivers/char/broadcom/bcm2835_smi_dev.c
@@ -0,0 +1,402 @@
+/**
+ * Character device driver for Broadcom Secondary Memory Interface
+ *
+ * Written by Luke Wren <luke@raspberrypi.org>
+ * Copyright (c) 2015, Raspberry Pi (Trading) Ltd.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2, as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+
+#include <linux/broadcom/bcm2835_smi.h>
+
+#define DEVICE_NAME "bcm2835-smi-dev"
+#define DRIVER_NAME "smi-dev-bcm2835"
+#define DEVICE_MINOR 0
+
+static struct cdev bcm2835_smi_cdev;
+static dev_t bcm2835_smi_devid;
+static struct class *bcm2835_smi_class;
+static struct device *bcm2835_smi_dev;
+
+struct bcm2835_smi_dev_instance {
+ struct device *dev;
+};
+
+static struct bcm2835_smi_instance *smi_inst;
+static struct bcm2835_smi_dev_instance *inst;
+
+static const char *const ioctl_names[] = {
+ "READ_SETTINGS",
+ "WRITE_SETTINGS",
+ "ADDRESS"
+};
+
+/****************************************************************************
+*
+* SMI chardev file ops
+*
+***************************************************************************/
+static long
+bcm2835_smi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ long ret = 0;
+
+ dev_info(inst->dev, "serving ioctl...");
+
+ switch (cmd) {
+ case BCM2835_SMI_IOC_GET_SETTINGS:{
+ struct smi_settings *settings;
+
+ dev_info(inst->dev, "Reading SMI settings to user.");
+ settings = bcm2835_smi_get_settings_from_regs(smi_inst);
+ if (copy_to_user((void *)arg, settings,
+ sizeof(struct smi_settings)))
+ dev_err(inst->dev, "settings copy failed.");
+ break;
+ }
+ case BCM2835_SMI_IOC_WRITE_SETTINGS:{
+ struct smi_settings *settings;
+
+ dev_info(inst->dev, "Setting user's SMI settings.");
+ settings = bcm2835_smi_get_settings_from_regs(smi_inst);
+ if (copy_from_user(settings, (void *)arg,
+ sizeof(struct smi_settings)))
+ dev_err(inst->dev, "settings copy failed.");
+ else
+ bcm2835_smi_set_regs_from_settings(smi_inst);
+ break;
+ }
+ case BCM2835_SMI_IOC_ADDRESS:
+ dev_info(inst->dev, "SMI address set: 0x%02x", (int)arg);
+ bcm2835_smi_set_address(smi_inst, arg);
+ break;
+ default:
+ dev_err(inst->dev, "invalid ioctl cmd: %d", cmd);
+ ret = -ENOTTY;
+ break;
+ }
+
+ return ret;
+}
+
+static int bcm2835_smi_open(struct inode *inode, struct file *file)
+{
+ int dev = iminor(inode);
+
+ dev_dbg(inst->dev, "SMI device opened.");
+
+ if (dev != DEVICE_MINOR) {
+ dev_err(inst->dev,
+ "bcm2835_smi_release: Unknown minor device: %d",
+ dev);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int bcm2835_smi_release(struct inode *inode, struct file *file)
+{
+ int dev = iminor(inode);
+
+ if (dev != DEVICE_MINOR) {
+ dev_err(inst->dev,
+ "bcm2835_smi_release: Unknown minor device %d", dev);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static ssize_t dma_bounce_user(
+ enum dma_transfer_direction dma_dir,
+ char __user *user_ptr,
+ size_t count,
+ struct bcm2835_smi_bounce_info *bounce)
+{
+ int chunk_size;
+ int chunk_no = 0;
+ int count_left = count;
+
+ while (count_left) {
+ int rv;
+ void *buf;
+
+ /* Wait for current chunk to complete: */
+ if (down_timeout(&bounce->callback_sem,
+ msecs_to_jiffies(1000))) {
+ dev_err(inst->dev, "DMA bounce timed out");
+ count -= (count_left);
+ break;
+ }
+
+ if (bounce->callback_sem.count >= DMA_BOUNCE_BUFFER_COUNT - 1)
+ dev_err(inst->dev, "WARNING: Ring buffer overflow");
+ chunk_size = count_left > DMA_BOUNCE_BUFFER_SIZE ?
+ DMA_BOUNCE_BUFFER_SIZE : count_left;
+ buf = bounce->buffer[chunk_no % DMA_BOUNCE_BUFFER_COUNT];
+ if (dma_dir == DMA_DEV_TO_MEM)
+ rv = copy_to_user(user_ptr, buf, chunk_size);
+ else
+ rv = copy_from_user(buf, user_ptr, chunk_size);
+ if (rv)
+ dev_err(inst->dev, "copy_*_user() failed!: %d", rv);
+ user_ptr += chunk_size;
+ count_left -= chunk_size;
+ chunk_no++;
+ }
+ return count;
+}
+
+static ssize_t
+bcm2835_read_file(struct file *f, char __user *user_ptr,
+ size_t count, loff_t *offs)
+{
+ int odd_bytes;
+
+ dev_dbg(inst->dev, "User reading %zu bytes from SMI.", count);
+ /* We don't want to DMA a number of bytes % 4 != 0 (32 bit FIFO) */
+ if (count > DMA_THRESHOLD_BYTES)
+ odd_bytes = count & 0x3;
+ else
+ odd_bytes = count;
+ count -= odd_bytes;
+ if (count) {
+ struct bcm2835_smi_bounce_info *bounce;
+
+ count = bcm2835_smi_user_dma(smi_inst,
+ DMA_DEV_TO_MEM, user_ptr, count,
+ &bounce);
+ if (count)
+ count = dma_bounce_user(DMA_DEV_TO_MEM, user_ptr,
+ count, bounce);
+ }
+ if (odd_bytes) {
+ /* Read from FIFO directly if not using DMA */
+ uint8_t buf[DMA_THRESHOLD_BYTES];
+
+ bcm2835_smi_read_buf(smi_inst, buf, odd_bytes);
+ if (copy_to_user(user_ptr, buf, odd_bytes))
+ dev_err(inst->dev, "copy_to_user() failed.");
+ count += odd_bytes;
+
+ }
+ return count;
+}
+
+static ssize_t
+bcm2835_write_file(struct file *f, const char __user *user_ptr,
+ size_t count, loff_t *offs)
+{
+ int odd_bytes;
+
+ dev_dbg(inst->dev, "User writing %zu bytes to SMI.", count);
+ if (count > DMA_THRESHOLD_BYTES)
+ odd_bytes = count & 0x3;
+ else
+ odd_bytes = count;
+ count -= odd_bytes;
+ if (count) {
+ struct bcm2835_smi_bounce_info *bounce;
+
+ count = bcm2835_smi_user_dma(smi_inst,
+ DMA_MEM_TO_DEV, (char __user *)user_ptr, count,
+ &bounce);
+ if (count)
+ count = dma_bounce_user(DMA_MEM_TO_DEV,
+ (char __user *)user_ptr,
+ count, bounce);
+ }
+ if (odd_bytes) {
+ uint8_t buf[DMA_THRESHOLD_BYTES];
+
+ if (copy_from_user(buf, user_ptr, odd_bytes))
+ dev_err(inst->dev, "copy_from_user() failed.");
+ else
+ bcm2835_smi_write_buf(smi_inst, buf, odd_bytes);
+ count += odd_bytes;
+ }
+ return count;
+}
+
+static const struct file_operations
+bcm2835_smi_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = bcm2835_smi_ioctl,
+ .open = bcm2835_smi_open,
+ .release = bcm2835_smi_release,
+ .read = bcm2835_read_file,
+ .write = bcm2835_write_file,
+};
+
+
+/****************************************************************************
+*
+* bcm2835_smi_probe - called when the driver is loaded.
+*
+***************************************************************************/
+
+static int bcm2835_smi_dev_probe(struct platform_device *pdev)
+{
+ int err;
+ void *ptr_err;
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node, *smi_node;
+
+ if (!node) {
+ dev_err(dev, "No device tree node supplied!");
+ return -EINVAL;
+ }
+
+ smi_node = of_parse_phandle(node, "smi_handle", 0);
+
+ if (!smi_node) {
+ dev_err(dev, "No such property: smi_handle");
+ return -ENXIO;
+ }
+
+ smi_inst = bcm2835_smi_get(smi_node);
+
+ if (!smi_inst)
+ return -EPROBE_DEFER;
+
+ /* Allocate buffers and instance data */
+
+ inst = devm_kzalloc(dev, sizeof(*inst), GFP_KERNEL);
+
+ if (!inst)
+ return -ENOMEM;
+
+ inst->dev = dev;
+
+ /* Create character device entries */
+
+ err = alloc_chrdev_region(&bcm2835_smi_devid,
+ DEVICE_MINOR, 1, DEVICE_NAME);
+ if (err != 0) {
+ dev_err(inst->dev, "unable to allocate device number");
+ return -ENOMEM;
+ }
+ cdev_init(&bcm2835_smi_cdev, &bcm2835_smi_fops);
+ bcm2835_smi_cdev.owner = THIS_MODULE;
+ err = cdev_add(&bcm2835_smi_cdev, bcm2835_smi_devid, 1);
+ if (err != 0) {
+ dev_err(inst->dev, "unable to register device");
+ err = -ENOMEM;
+ goto failed_cdev_add;
+ }
+
+ /* Create sysfs entries */
+
+ bcm2835_smi_class = class_create(THIS_MODULE, DEVICE_NAME);
+ ptr_err = bcm2835_smi_class;
+ if (IS_ERR(ptr_err))
+ goto failed_class_create;
+
+ bcm2835_smi_dev = device_create(bcm2835_smi_class, NULL,
+ bcm2835_smi_devid, NULL,
+ "smi");
+ ptr_err = bcm2835_smi_dev;
+ if (IS_ERR(ptr_err))
+ goto failed_device_create;
+
+ dev_info(inst->dev, "initialised");
+
+ return 0;
+
+failed_device_create:
+ class_destroy(bcm2835_smi_class);
+failed_class_create:
+ cdev_del(&bcm2835_smi_cdev);
+ err = PTR_ERR(ptr_err);
+failed_cdev_add:
+ unregister_chrdev_region(bcm2835_smi_devid, 1);
+ dev_err(dev, "could not load bcm2835_smi_dev");
+ return err;
+}
+
+/****************************************************************************
+*
+* bcm2835_smi_remove - called when the driver is unloaded.
+*
+***************************************************************************/
+
+static int bcm2835_smi_dev_remove(struct platform_device *pdev)
+{
+ device_destroy(bcm2835_smi_class, bcm2835_smi_devid);
+ class_destroy(bcm2835_smi_class);
+ cdev_del(&bcm2835_smi_cdev);
+ unregister_chrdev_region(bcm2835_smi_devid, 1);
+
+ dev_info(inst->dev, "SMI character dev removed - OK");
+ return 0;
+}
+
+/****************************************************************************
+*
+* Register the driver with device tree
+*
+***************************************************************************/
+
+static const struct of_device_id bcm2835_smi_dev_of_match[] = {
+ {.compatible = "brcm,bcm2835-smi-dev",},
+ { /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, bcm2835_smi_dev_of_match);
+
+static struct platform_driver bcm2835_smi_dev_driver = {
+ .probe = bcm2835_smi_dev_probe,
+ .remove = bcm2835_smi_dev_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = bcm2835_smi_dev_of_match,
+ },
+};
+
+module_platform_driver(bcm2835_smi_dev_driver);
+
+MODULE_ALIAS("platform:smi-dev-bcm2835");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION(
+ "Character device driver for BCM2835's secondary memory interface");
+MODULE_AUTHOR("Luke Wren <luke@raspberrypi.org>");