mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
402 lines
11 KiB
402 lines
11 KiB
/** |
|
* Character device driver for Broadcom Secondary Memory Interface |
|
* |
|
* Written by Luke Wren <[email protected]> |
|
* 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 <[email protected]>");
|
|
|