/**
|
* @note Copyright (C) 2016 Philippe Gerum <rpm@xenomai.org>
|
*
|
* This program is free software; you can redistribute it and/or
|
* modify it under the terms of the GNU General Public License as
|
* published by the Free Software Foundation; either version 2 of the
|
* License, or (at your option) any later version.
|
*
|
* 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.
|
*
|
* You should have received a copy of the GNU General Public License
|
* along with this program; if not, write to the Free Software
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
*/
|
#include <linux/module.h>
|
#include <linux/init.h>
|
#include <linux/device.h>
|
#include <linux/slab.h>
|
#include <linux/mutex.h>
|
#include <linux/err.h>
|
#include <linux/spi/spi.h>
|
#include <linux/gpio.h>
|
#include "spi-master.h"
|
|
static inline
|
struct device *to_kdev(struct rtdm_spi_remote_slave *slave)
|
{
|
return rtdm_dev_to_kdev(&slave->dev);
|
}
|
|
static inline struct rtdm_spi_remote_slave *fd_to_slave(struct rtdm_fd *fd)
|
{
|
struct rtdm_device *dev = rtdm_fd_device(fd);
|
|
return container_of(dev, struct rtdm_spi_remote_slave, dev);
|
}
|
|
static int update_slave_config(struct rtdm_spi_remote_slave *slave,
|
struct rtdm_spi_config *config)
|
{
|
struct rtdm_spi_config old_config;
|
struct rtdm_spi_master *master = slave->master;
|
int ret;
|
|
rtdm_mutex_lock(&master->bus_lock);
|
|
old_config = slave->config;
|
slave->config = *config;
|
ret = slave->master->ops->configure(slave);
|
if (ret) {
|
slave->config = old_config;
|
rtdm_mutex_unlock(&master->bus_lock);
|
return ret;
|
}
|
|
rtdm_mutex_unlock(&master->bus_lock);
|
|
dev_info(to_kdev(slave),
|
"configured mode %d, %s%s%s%s%u bits/w, %u Hz max\n",
|
(int) (slave->config.mode & (SPI_CPOL | SPI_CPHA)),
|
(slave->config.mode & SPI_CS_HIGH) ? "cs_high, " : "",
|
(slave->config.mode & SPI_LSB_FIRST) ? "lsb, " : "",
|
(slave->config.mode & SPI_3WIRE) ? "3wire, " : "",
|
(slave->config.mode & SPI_LOOP) ? "loopback, " : "",
|
slave->config.bits_per_word,
|
slave->config.speed_hz);
|
|
return 0;
|
}
|
|
static int spi_master_open(struct rtdm_fd *fd, int oflags)
|
{
|
struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
|
struct rtdm_spi_master *master = slave->master;
|
|
if (master->ops->open)
|
return master->ops->open(slave);
|
|
return 0;
|
}
|
|
static void spi_master_close(struct rtdm_fd *fd)
|
{
|
struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
|
struct rtdm_spi_master *master = slave->master;
|
rtdm_lockctx_t c;
|
|
rtdm_lock_get_irqsave(&master->lock, c);
|
|
if (master->cs == slave)
|
master->cs = NULL;
|
|
rtdm_lock_put_irqrestore(&master->lock, c);
|
|
if (master->ops->close)
|
master->ops->close(slave);
|
}
|
|
static int do_chip_select(struct rtdm_spi_remote_slave *slave)
|
{ /* master->bus_lock held */
|
struct rtdm_spi_master *master = slave->master;
|
rtdm_lockctx_t c;
|
int state;
|
|
if (slave->config.speed_hz == 0)
|
return -EINVAL; /* Setup is missing. */
|
|
/* Serialize with spi_master_close() */
|
rtdm_lock_get_irqsave(&master->lock, c);
|
|
if (master->cs != slave) {
|
if (slave->cs_gpiod) {
|
state = !!(slave->config.mode & SPI_CS_HIGH);
|
gpiod_set_raw_value(slave->cs_gpiod, state);
|
} else
|
master->ops->chip_select(slave, true);
|
master->cs = slave;
|
}
|
|
rtdm_lock_put_irqrestore(&master->lock, c);
|
|
return 0;
|
}
|
|
static void do_chip_deselect(struct rtdm_spi_remote_slave *slave)
|
{ /* master->bus_lock held */
|
struct rtdm_spi_master *master = slave->master;
|
rtdm_lockctx_t c;
|
int state;
|
|
rtdm_lock_get_irqsave(&master->lock, c);
|
|
if (slave->cs_gpiod) {
|
state = !(slave->config.mode & SPI_CS_HIGH);
|
gpiod_set_raw_value(slave->cs_gpiod, state);
|
} else
|
master->ops->chip_select(slave, false);
|
|
master->cs = NULL;
|
|
rtdm_lock_put_irqrestore(&master->lock, c);
|
}
|
|
static int spi_master_ioctl_rt(struct rtdm_fd *fd,
|
unsigned int request, void *arg)
|
{
|
struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
|
struct rtdm_spi_master *master = slave->master;
|
struct rtdm_spi_config config;
|
int ret, len;
|
|
switch (request) {
|
case SPI_RTIOC_SET_CONFIG:
|
ret = rtdm_safe_copy_from_user(fd, &config,
|
arg, sizeof(config));
|
if (ret == 0)
|
ret = update_slave_config(slave, &config);
|
break;
|
case SPI_RTIOC_GET_CONFIG:
|
rtdm_mutex_lock(&master->bus_lock);
|
config = slave->config;
|
rtdm_mutex_unlock(&master->bus_lock);
|
ret = rtdm_safe_copy_to_user(fd, arg,
|
&config, sizeof(config));
|
break;
|
case SPI_RTIOC_TRANSFER:
|
ret = -EINVAL;
|
if (master->ops->transfer_iobufs) {
|
rtdm_mutex_lock(&master->bus_lock);
|
ret = do_chip_select(slave);
|
if (ret == 0) {
|
ret = master->ops->transfer_iobufs(slave);
|
do_chip_deselect(slave);
|
}
|
rtdm_mutex_unlock(&master->bus_lock);
|
}
|
break;
|
case SPI_RTIOC_TRANSFER_N:
|
ret = -EINVAL;
|
if (master->ops->transfer_iobufs_n) {
|
len = (long)arg;
|
rtdm_mutex_lock(&master->bus_lock);
|
ret = do_chip_select(slave);
|
if (ret == 0) {
|
ret = master->ops->transfer_iobufs_n(slave, len);
|
do_chip_deselect(slave);
|
}
|
rtdm_mutex_unlock(&master->bus_lock);
|
}
|
break;
|
default:
|
ret = -ENOSYS;
|
}
|
|
return ret;
|
}
|
|
static int spi_master_ioctl_nrt(struct rtdm_fd *fd,
|
unsigned int request, void *arg)
|
{
|
struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
|
struct rtdm_spi_master *master = slave->master;
|
struct rtdm_spi_iobufs iobufs;
|
int ret;
|
|
switch (request) {
|
case SPI_RTIOC_SET_IOBUFS:
|
ret = rtdm_safe_copy_from_user(fd, &iobufs,
|
arg, sizeof(iobufs));
|
if (ret)
|
break;
|
/*
|
* No transfer can happen without I/O buffers being
|
* set, and I/O buffers cannot be reset, therefore we
|
* need no serialization with the transfer code here.
|
*/
|
mutex_lock(&slave->ctl_lock);
|
ret = master->ops->set_iobufs(slave, &iobufs);
|
mutex_unlock(&slave->ctl_lock);
|
if (ret == 0)
|
ret = rtdm_safe_copy_to_user(fd, arg,
|
&iobufs, sizeof(iobufs));
|
break;
|
default:
|
ret = -EINVAL;
|
}
|
|
return ret;
|
}
|
|
static ssize_t spi_master_read_rt(struct rtdm_fd *fd,
|
void __user *u_buf, size_t len)
|
{
|
struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
|
struct rtdm_spi_master *master = slave->master;
|
void *rx;
|
int ret;
|
|
if (len == 0)
|
return 0;
|
|
rx = xnmalloc(len);
|
if (rx == NULL)
|
return -ENOMEM;
|
|
rtdm_mutex_lock(&master->bus_lock);
|
ret = do_chip_select(slave);
|
if (ret == 0) {
|
ret = master->ops->read(slave, rx, len);
|
do_chip_deselect(slave);
|
}
|
rtdm_mutex_unlock(&master->bus_lock);
|
if (ret > 0)
|
ret = rtdm_safe_copy_to_user(fd, u_buf, rx, ret);
|
|
xnfree(rx);
|
|
return ret;
|
}
|
|
static ssize_t spi_master_write_rt(struct rtdm_fd *fd,
|
const void __user *u_buf, size_t len)
|
{
|
struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
|
struct rtdm_spi_master *master = slave->master;
|
void *tx;
|
int ret;
|
|
if (len == 0)
|
return 0;
|
|
tx = xnmalloc(len);
|
if (tx == NULL)
|
return -ENOMEM;
|
|
ret = rtdm_safe_copy_from_user(fd, tx, u_buf, len);
|
if (ret == 0) {
|
rtdm_mutex_lock(&master->bus_lock);
|
ret = do_chip_select(slave);
|
if (ret == 0) {
|
ret = master->ops->write(slave, tx, len);
|
do_chip_deselect(slave);
|
}
|
rtdm_mutex_unlock(&master->bus_lock);
|
}
|
|
xnfree(tx);
|
|
return ret;
|
}
|
|
static void iobufs_vmopen(struct vm_area_struct *vma)
|
{
|
struct rtdm_spi_remote_slave *slave = vma->vm_private_data;
|
|
atomic_inc(&slave->mmap_refs);
|
dev_dbg(slave_to_kdev(slave), "mapping added\n");
|
}
|
|
static void iobufs_vmclose(struct vm_area_struct *vma)
|
{
|
struct rtdm_spi_remote_slave *slave = vma->vm_private_data;
|
|
if (atomic_dec_and_test(&slave->mmap_refs)) {
|
slave->master->ops->mmap_release(slave);
|
dev_dbg(slave_to_kdev(slave), "mapping released\n");
|
}
|
}
|
|
static struct vm_operations_struct iobufs_vmops = {
|
.open = iobufs_vmopen,
|
.close = iobufs_vmclose,
|
};
|
|
static int spi_master_mmap(struct rtdm_fd *fd, struct vm_area_struct *vma)
|
{
|
struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
|
int ret;
|
|
if (slave->master->ops->mmap_iobufs == NULL)
|
return -EINVAL;
|
|
ret = slave->master->ops->mmap_iobufs(slave, vma);
|
if (ret)
|
return ret;
|
|
dev_dbg(slave_to_kdev(slave), "mapping created\n");
|
atomic_inc(&slave->mmap_refs);
|
|
if (slave->master->ops->mmap_release) {
|
vma->vm_ops = &iobufs_vmops;
|
vma->vm_private_data = slave;
|
}
|
|
return 0;
|
}
|
|
static char *spi_slave_devnode(struct device *dev, umode_t *mode)
|
{
|
return kasprintf(GFP_KERNEL, "rtdm/%s/%s",
|
dev->class->name,
|
dev_name(dev));
|
}
|
|
struct rtdm_spi_master *
|
__rtdm_spi_alloc_master(struct device *dev, size_t size, int off)
|
{
|
struct rtdm_spi_master *master;
|
struct spi_controller *ctlr;
|
|
ctlr = spi_alloc_master(dev, size);
|
if (ctlr == NULL)
|
return NULL;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,1,0)
|
ctlr->use_gpio_descriptors = true;
|
#endif
|
master = (void *)(ctlr + 1) + off;
|
master->controller = ctlr;
|
spi_master_set_devdata(ctlr, master);
|
|
return master;
|
}
|
EXPORT_SYMBOL_GPL(__rtdm_spi_alloc_master);
|
|
int __rtdm_spi_setup_driver(struct rtdm_spi_master *master)
|
{
|
master->classname = kstrdup(
|
dev_name(&master->controller->dev), GFP_KERNEL);
|
master->devclass = class_create(THIS_MODULE,
|
master->classname);
|
if (IS_ERR(master->devclass)) {
|
kfree(master->classname);
|
printk(XENO_ERR "cannot create sysfs class\n");
|
return PTR_ERR(master->devclass);
|
}
|
|
master->devclass->devnode = spi_slave_devnode;
|
master->cs = NULL;
|
|
master->driver.profile_info = (struct rtdm_profile_info)
|
RTDM_PROFILE_INFO(rtdm_spi_master,
|
RTDM_CLASS_SPI,
|
master->subclass,
|
0);
|
master->driver.device_flags = RTDM_NAMED_DEVICE;
|
master->driver.base_minor = 0;
|
master->driver.device_count = 256;
|
master->driver.context_size = 0;
|
master->driver.ops = (struct rtdm_fd_ops){
|
.open = spi_master_open,
|
.close = spi_master_close,
|
.read_rt = spi_master_read_rt,
|
.write_rt = spi_master_write_rt,
|
.ioctl_rt = spi_master_ioctl_rt,
|
.ioctl_nrt = spi_master_ioctl_nrt,
|
.mmap = spi_master_mmap,
|
};
|
|
rtdm_drv_set_sysclass(&master->driver, master->devclass);
|
|
INIT_LIST_HEAD(&master->slaves);
|
rtdm_lock_init(&master->lock);
|
rtdm_mutex_init(&master->bus_lock);
|
|
return 0;
|
}
|
|
static int spi_transfer_one_unimp(struct spi_master *master,
|
struct spi_device *spi,
|
struct spi_transfer *tfr)
|
{
|
return -ENODEV;
|
}
|
|
int rtdm_spi_add_master(struct rtdm_spi_master *master)
|
{
|
struct spi_controller *ctlr = master->controller;
|
|
/*
|
* Prevent the transfer handler to be called from the regular
|
* SPI stack, just in case.
|
*/
|
ctlr->transfer_one = spi_transfer_one_unimp;
|
master->devclass = NULL;
|
|
/*
|
* Add the core SPI driver, devices on the bus will be
|
* enumerated, handed to spi_device_probe().
|
*/
|
return spi_register_controller(ctlr);
|
}
|
EXPORT_SYMBOL_GPL(rtdm_spi_add_master);
|
|
void rtdm_spi_remove_master(struct rtdm_spi_master *master)
|
{
|
struct class *class = master->devclass;
|
char *classname = master->classname;
|
|
rtdm_mutex_destroy(&master->bus_lock);
|
spi_unregister_controller(master->controller);
|
rtdm_drv_set_sysclass(&master->driver, NULL);
|
class_destroy(class);
|
kfree(classname);
|
}
|
EXPORT_SYMBOL_GPL(rtdm_spi_remove_master);
|
|
MODULE_LICENSE("GPL");
|