QortalOS Brooklyn for Raspberry Pi 4
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

/**
* 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]>");