/** * Character device driver for Broadcom Secondary Memory Interface * Streaming / Polling * * Based on char device by Luke Wren * Copyright (c) 2015, Raspberry Pi (Trading) Ltd. * * Written by David Michaeli (cariboulabs.co@gmail.com) * Copyright (c) 2022, CaribouLabs 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. */ /* * Contribution by matteo serva * https://github.com/matteoserva * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smi_stream_dev.h" // MODULE SPECIFIC PARAMETERS // the modules.d line is as follows: "options smi_stream_dev fifo_mtu_multiplier=6 addr_dir_offset=2 addr_ch_offset=3" static int fifo_mtu_multiplier = 6;// How many MTUs to allocate for kfifo's static int addr_dir_offset = 2; // GPIO_SA[4:0] offset of the channel direction static int addr_ch_offset = 3; // GPIO_SA[4:0] offset of the channel select #define SMI_TRANSFER_MULTIPLIER 64 module_param(fifo_mtu_multiplier, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); module_param(addr_dir_offset, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); module_param(addr_ch_offset, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); MODULE_PARM_DESC(fifo_mtu_multiplier, "the number of MTUs (N*MTU_SIZE) to allocate for kfifo's (default 6) valid: [3..33]"); MODULE_PARM_DESC(addr_dir_offset, "GPIO_SA[4:0] offset of the channel direction (default cariboulite 2), valid: [0..4] or (-1) if unused"); MODULE_PARM_DESC(addr_ch_offset, "GPIO_SA[4:0] offset of the channel select (default cariboulite 3), valid: [0..4] or (-1) if unused"); /***************************************************************************/ struct bcm2835_smi_dev_instance { struct device *dev; struct bcm2835_smi_instance *smi_inst; // address related unsigned int cur_address; int address_changed; // flags int invalidate_rx_buffers; int invalidate_tx_buffers; unsigned int count_since_refresh; struct kfifo rx_fifo; struct kfifo tx_fifo; uint8_t* rx_fifo_buffer; uint8_t* tx_fifo_buffer; smi_stream_state_en state; struct mutex read_lock; struct mutex write_lock; spinlock_t state_lock; wait_queue_head_t poll_event; uint32_t current_read_chunk; uint32_t counter_missed; bool readable; bool writeable; bool transfer_thread_running; bool reader_waiting_sema; bool writer_waiting_sema; }; // Prototypes /***************************************************************************/ ssize_t stream_smi_user_dma(struct bcm2835_smi_instance *inst, enum dma_transfer_direction dma_dir, struct bcm2835_smi_bounce_info **bounce, int buff_num); int transfer_thread_init(struct bcm2835_smi_dev_instance *inst, enum dma_transfer_direction dir,dma_async_tx_callback callback); static void stream_smi_read_dma_callback(void *param); static void stream_smi_write_dma_callback(void *param); void transfer_thread_stop(struct bcm2835_smi_dev_instance *inst); void print_smil_registers(void); static struct bcm2835_smi_dev_instance *inst = NULL; static const char *const ioctl_names[] = { "READ_SETTINGS", "WRITE_SETTINGS", "ADDRESS", "GET_NATIVE_BUF_SIZE", "SET_NON_BLOCK_READ", "SET_NON_BLOCK_WRITE", "SET_STREAM_STATE" }; #define BUSY_WAIT_WHILE_TIMEOUT(C,T,R) {int t = (T); while ((C) && t>0){t--;} (R)=t>0;} /***************************************************************************/ static void write_smi_reg(struct bcm2835_smi_instance *inst, u32 val, unsigned reg) { writel(val, inst->smi_regs_ptr + reg); mb(); } /***************************************************************************/ static u32 read_smi_reg(struct bcm2835_smi_instance *inst, unsigned reg) { return readl(inst->smi_regs_ptr + reg); } /***************************************************************************/ void print_smil_registers() { struct bcm2835_smi_instance *smi_inst = inst->smi_inst; unsigned int smics = read_smi_reg(smi_inst, SMICS); unsigned int smil = read_smi_reg(smi_inst, SMIL); unsigned int smidc = read_smi_reg(smi_inst, SMIDC); unsigned int smidsw0 = read_smi_reg(smi_inst,SMIDSW0); dev_info(inst->dev, "regs: smics %08X smil %08X smids %08X smisw0 %08X",smics,smil,smidc,smidsw0); } /***************************************************************************/ void print_smil_registers_ext(const char* b) { struct bcm2835_smi_instance *smi_inst = inst->smi_inst; unsigned int smics = read_smi_reg(smi_inst, SMICS); unsigned int smil = read_smi_reg(smi_inst, SMIL); unsigned int smidc = read_smi_reg(smi_inst, SMIDC); unsigned int smidsw0 = read_smi_reg(smi_inst,SMIDSW0); dev_info(inst->dev, "%s: regs: smics %08X smil %08X smids %08X smisw0 %08X",b,smics,smil,smidc,smidsw0); } /***************************************************************************/ static unsigned int calc_address_from_state(smi_stream_state_en state) { unsigned int return_val = (smi_stream_dir_device_to_smi<dev, "Set STREAMING_STATUS = %d, cur_addr = %d", new_state, new_address); spin_lock(&inst->state_lock); // in any case if we want to change the state // then stop the current transfer and update the new state. if(new_state != inst->state) { // stop the transter transfer_thread_stop(inst); if(smi_is_active(inst->smi_inst)) { spin_unlock(&inst->state_lock); return -EAGAIN; } // update the state from current state inst->state = smi_stream_idle; bcm2835_smi_set_address(inst->smi_inst, calc_address_from_state(smi_stream_idle)); ret = 0; //now state is idle } // else if the state is the same, do nothing else { spin_unlock(&inst->state_lock); dev_info(inst->dev, "State is the same as before"); return 0; } // Only if the new state is not idle (rx0, rx1 ot tx) setup a new transfer if(new_state != smi_stream_idle) { bcm2835_smi_set_address(inst->smi_inst, new_address); if (new_state == smi_stream_tx_channel) { // remove all data inside the tx_fifo if (mutex_lock_interruptible(&inst->write_lock)) { return -EINTR; } kfifo_reset(&inst->tx_fifo); mutex_unlock(&inst->write_lock); inst->writeable = true; wake_up_interruptible(&inst->poll_event); //ret = transfer_thread_init(inst, DMA_MEM_TO_DEV, stream_smi_write_dma_callback); mb(); spin_unlock(&inst->state_lock); // return the success return ret; } else { ret = transfer_thread_init(inst, DMA_DEV_TO_MEM, stream_smi_read_dma_callback); } // if starting the transfer succeeded update the state if (!ret) { inst->state = new_state; } // if failed, go back to idle else { bcm2835_smi_set_address(inst->smi_inst, calc_address_from_state(smi_stream_idle)); inst->state = smi_stream_idle; } } mb(); spin_unlock(&inst->state_lock); // return the success return ret; } /***************************************************************************/ static void smi_setup_clock(struct bcm2835_smi_instance *inst) { } /***************************************************************************/ static inline int smi_enabled(struct bcm2835_smi_instance *inst) { return read_smi_reg(inst, SMICS) & SMICS_ENABLE; } /***************************************************************************/ static int smi_disable_sync(struct bcm2835_smi_instance *smi_inst) { int smics_temp = 0; int success = 0; int errors = 0; //dev_info(inst->dev, "smi disable sync enter"); /* Disable the peripheral: */ smics_temp = read_smi_reg(smi_inst, SMICS) & ~(SMICS_ENABLE | SMICS_WRITE); write_smi_reg(smi_inst, smics_temp, SMICS); // wait for the ENABLE to go low BUSY_WAIT_WHILE_TIMEOUT(smi_enabled(smi_inst), 100000U, success); if (!success) { //dev_info(inst->dev, "error disable sync. %u %08X", smi_enabled(smi_inst), read_smi_reg(smi_inst, SMICS)); errors = -1; } //print_smil_registers(); //dev_info(inst->dev, "smi disable sync exit"); return errors; } /***************************************************************************/ static void smi_refresh_dma_command(struct bcm2835_smi_instance *smi_inst, int num_transfers) { int smics_temp = 0; //print_smil_registers_ext("refresh 1"); write_smi_reg(smi_inst, SMI_TRANSFER_MULTIPLIER*num_transfers, SMIL); //to avoid stopping and restarting //print_smil_registers_ext("refresh 2"); // Start the transaction smics_temp = read_smi_reg(smi_inst, SMICS); smics_temp |= SMICS_START; //smics_temp &= ~(SMICS_PVMODE); write_smi_reg(smi_inst, smics_temp & 0xffff, SMICS); inst->count_since_refresh = 0; //print_smil_registers_ext("refresh 3"); } /***************************************************************************/ static int smi_init_programmed_transfer(struct bcm2835_smi_instance *smi_inst, enum dma_transfer_direction dma_dir, int num_transfers) { int smics_temp = 0; int success = 0; dev_info(inst->dev, "smi_init_programmed_transfer"); print_smil_registers_ext("init 1"); write_smi_reg(inst->smi_inst, 0x0, SMIL); print_smil_registers_ext("init 2"); smics_temp = read_smi_reg(smi_inst, SMICS); /* Program the transfer count: */ write_smi_reg(smi_inst, num_transfers, SMIL); print_smil_registers_ext("init 3"); /* re-enable and start: */ smics_temp |= SMICS_CLEAR; smics_temp |= SMICS_ENABLE; if(dma_dir == DMA_MEM_TO_DEV) { smics_temp |= SMICS_WRITE; } write_smi_reg(smi_inst, smics_temp, SMICS); print_smil_registers_ext("init 4"); /* IO barrier - to be sure that the last request have been dispatched in the correct order */ mb(); // busy wait as long as the transaction is active (taking place) BUSY_WAIT_WHILE_TIMEOUT(smi_is_active(smi_inst), 1000000U, success); if (!success) { dev_info(inst->dev, "smi_init_programmed_transfer error disable. %u %08X", smi_enabled(smi_inst), read_smi_reg(smi_inst, SMICS)); return -2; } // Clear the FIFO (reset it to zero contents) write_smi_reg(smi_inst, smics_temp, SMICS); print_smil_registers_ext("init 5"); return 0; } /**************************************************************************** * * SMI chardev file ops * ***************************************************************************/ static long smi_stream_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(inst->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(inst->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(inst->smi_inst); } break; } //------------------------------- case BCM2835_SMI_IOC_ADDRESS: { dev_info(inst->dev, "SMI address set: 0x%02x", (int)arg); //bcm2835_smi_set_address(inst->smi_inst, arg); break; } //------------------------------- case SMI_STREAM_IOC_SET_STREAM_IN_CHANNEL: { //dev_info(inst->dev, "SMI channel: 0x%02x", (int)arg); //set_address_channel((smi_stream_channel_en)arg); break; } //------------------------------- case SMI_STREAM_IOC_GET_NATIVE_BUF_SIZE: { size_t size = (size_t)(DMA_BOUNCE_BUFFER_SIZE); dev_info(inst->dev, "Reading native buffer size information"); if (copy_to_user((void *)arg, &size, sizeof(size_t))) { dev_err(inst->dev, "buffer sizes copy failed."); } break; } //------------------------------- case SMI_STREAM_IOC_SET_STREAM_STATUS: { ret = set_state((smi_stream_state_en)arg); break; } //------------------------------- case SMI_STREAM_IOC_SET_FIFO_MULT: { int temp = (int)arg; if (temp > 20 || temp < 2) { dev_err(inst->dev, "Parameter error: 2dev, "Setting FIFO size multiplier to %d", temp); fifo_mtu_multiplier = temp; break; } //------------------------------- case SMI_STREAM_IOC_SET_ADDR_DIR_OFFSET: { int temp = (int)arg; if (temp > 4 || temp < -1) { dev_err(inst->dev, "Parameter error: 0<=addr_dir_offset<=4 or (-1 - unused), got %d", temp); return -EINVAL; } dev_info(inst->dev, "Setting address direction indication offset to %d", temp); addr_dir_offset = temp; break; } //------------------------------- case SMI_STREAM_IOC_SET_ADDR_CH_OFFSET: { int temp = (int)arg; if (temp > 4 || temp < -1) { dev_err(inst->dev, "Parameter error: 0<=addr_ch_offset<=4 or (-1 - unused), got %d", temp); return -EINVAL; } dev_info(inst->dev, "Setting address channel indication offset to %d", temp); addr_ch_offset = temp; break; } //------------------------------- case SMI_STREAM_IOC_GET_FIFO_MULT: { dev_dbg(inst->dev, "Reading FIFO size multiplier of %d", fifo_mtu_multiplier); if (copy_to_user((void *)arg, &fifo_mtu_multiplier, sizeof(fifo_mtu_multiplier))) { dev_err(inst->dev, "fifo_mtu_multiplier copy failed."); } break; } //------------------------------- case SMI_STREAM_IOC_GET_ADDR_DIR_OFFSET: { dev_dbg(inst->dev, "Reading address direction indication offset of %d", addr_dir_offset); if (copy_to_user((void *)arg, &addr_dir_offset, sizeof(addr_dir_offset))) { dev_err(inst->dev, "addr_dir_offset copy failed."); } break; } //------------------------------- case SMI_STREAM_IOC_GET_ADDR_CH_OFFSET: { dev_dbg(inst->dev, "Reading address channel indication offset of %d", addr_ch_offset); if (copy_to_user((void *)arg, &addr_ch_offset, sizeof(addr_ch_offset))) { dev_err(inst->dev, "addr_ch_offset copy failed."); } break; } //------------------------------- case SMI_STREAM_IOC_FLUSH_FIFO: { // moved to read file operation break; } //------------------------------- default: dev_err(inst->dev, "invalid ioctl cmd: %d", cmd); ret = -ENOTTY; break; } return ret; } /**************************************************************************** * * SMI DMA functions * ***************************************************************************/ static void stream_smi_read_dma_callback(void *param) { /* Notify the bottom half that a chunk is ready for user copy */ struct bcm2835_smi_dev_instance *inst = (struct bcm2835_smi_dev_instance *)param; struct bcm2835_smi_instance *smi_inst = inst->smi_inst; uint8_t* buffer_pos; smi_refresh_dma_command(smi_inst, DMA_BOUNCE_BUFFER_SIZE/4); buffer_pos = (uint8_t*) smi_inst->bounce.buffer[0]; buffer_pos = &buffer_pos[ (DMA_BOUNCE_BUFFER_SIZE/4) * (inst->current_read_chunk % 4)]; if(kfifo_avail(&inst->rx_fifo) >=DMA_BOUNCE_BUFFER_SIZE/4) { kfifo_in(&inst->rx_fifo, buffer_pos, DMA_BOUNCE_BUFFER_SIZE/4); } else { inst->counter_missed++; } if(!(inst->current_read_chunk % 100 )) { dev_info(inst->dev,"init programmed read. missed: %u, sema %u",inst->counter_missed,smi_inst->bounce.callback_sem.count); } up(&smi_inst->bounce.callback_sem); inst->readable = true; wake_up_interruptible(&inst->poll_event); inst->current_read_chunk++; } /***************************************************************************/ static void stream_smi_check_and_restart(struct bcm2835_smi_dev_instance *inst) { struct bcm2835_smi_instance *smi_inst = inst->smi_inst; inst->count_since_refresh++; if( (inst->count_since_refresh )>= SMI_TRANSFER_MULTIPLIER) { int i; for(i = 0; i < 1000; i++) { if(!smi_is_active(smi_inst)) { break; } udelay(1); } if(i == 1000) { print_smil_registers_ext("write dma callback error 1000"); } smi_refresh_dma_command(smi_inst, DMA_BOUNCE_BUFFER_SIZE/4); } } /***************************************************************************/ static void stream_smi_write_dma_callback(void *param) { /* Notify the bottom half that a chunk is ready for user copy */ struct bcm2835_smi_dev_instance *inst = (struct bcm2835_smi_dev_instance *)param; struct bcm2835_smi_instance *smi_inst = inst->smi_inst; uint8_t* buffer_pos; stream_smi_check_and_restart(inst); inst->current_read_chunk++; buffer_pos = (uint8_t*) smi_inst->bounce.buffer[0]; buffer_pos = &buffer_pos[ (DMA_BOUNCE_BUFFER_SIZE/4) * (inst->current_read_chunk % 4)]; if(kfifo_len (&inst->tx_fifo) >= DMA_BOUNCE_BUFFER_SIZE/4) { int num_copied = kfifo_out(&inst->tx_fifo, buffer_pos, DMA_BOUNCE_BUFFER_SIZE/4); (void)num_copied; } else { inst->counter_missed++; } if(!(inst->current_read_chunk % 111 )) { dev_info(inst->dev,"init programmed write. missed: %u, sema %u, val %08X",inst->counter_missed,smi_inst->bounce.callback_sem.count,*(uint32_t*) &buffer_pos[0]); } up(&smi_inst->bounce.callback_sem); inst->writeable = true; wake_up_interruptible(&inst->poll_event); } /***************************************************************************/ static struct dma_async_tx_descriptor *stream_smi_dma_init_cyclic( struct bcm2835_smi_instance *inst, enum dma_transfer_direction dir, dma_async_tx_callback callback, void*param) { struct dma_async_tx_descriptor *desc = NULL; //printk(KERN_ERR DRIVER_NAME": SUBMIT_PREP %lu\n", (long unsigned int)(inst->dma_chan)); desc = dmaengine_prep_dma_cyclic(inst->dma_chan, inst->bounce.phys[0], DMA_BOUNCE_BUFFER_SIZE, DMA_BOUNCE_BUFFER_SIZE/4, dir,DMA_PREP_INTERRUPT | DMA_CTRL_ACK | DMA_PREP_FENCE); if (!desc) { dev_err(inst->dev, "read_sgl: dma slave preparation failed!"); return NULL; } desc->callback = callback; desc->callback_param = param; if (dmaengine_submit(desc) < 0) { return NULL; } return desc; } /**************************************************************************** * * transfer thread functions * ***************************************************************************/ int transfer_thread_init(struct bcm2835_smi_dev_instance *inst, enum dma_transfer_direction dir, dma_async_tx_callback callback) { unsigned int errors = 0; int ret; int success; dev_info(inst->dev, "Starting cyclic transfer, dma dir: %d", dir); inst->transfer_thread_running = true; /* Disable the peripheral: */ if(smi_disable_sync(inst->smi_inst)) { dev_err(inst->smi_inst->dev, "smi_disable_sync failed"); return -1; } write_smi_reg(inst->smi_inst, 0, SMIL); sema_init(&inst->smi_inst->bounce.callback_sem, 0); spin_lock(&inst->smi_inst->transaction_lock); ret = smi_init_programmed_transfer(inst->smi_inst, dir, DMA_BOUNCE_BUFFER_SIZE/4); if (ret != 0) { spin_unlock(&inst->smi_inst->transaction_lock); dev_err(inst->smi_inst->dev, "smi_init_programmed_transfer returned %d", ret); smi_disable_sync(inst->smi_inst); return -2; } else { spin_unlock(&inst->smi_inst->transaction_lock); } inst->current_read_chunk = 0; inst->counter_missed = 0; if(!errors) { struct dma_async_tx_descriptor *desc = NULL; struct bcm2835_smi_instance *smi_inst = inst->smi_inst; spin_lock(&smi_inst->transaction_lock); desc = stream_smi_dma_init_cyclic(smi_inst, dir, callback, inst); if(desc) { dma_async_issue_pending(smi_inst->dma_chan); } else { errors = 1; } spin_unlock(&smi_inst->transaction_lock); } smi_refresh_dma_command(inst->smi_inst, DMA_BOUNCE_BUFFER_SIZE/4); BUSY_WAIT_WHILE_TIMEOUT(!smi_is_active(inst->smi_inst), 1000000U, success); print_smil_registers_ext("post init 0"); return errors; } /***************************************************************************/ void transfer_thread_stop(struct bcm2835_smi_dev_instance *inst) { //int errors = 0; //dev_info(inst->dev, "Reader state became idle, terminating dma %u %u", (inst->address_changed) ,errors); print_smil_registers_ext("thread stop 0"); spin_lock(&inst->smi_inst->transaction_lock); dmaengine_terminate_sync(inst->smi_inst->dma_chan); spin_unlock(&inst->smi_inst->transaction_lock); //dev_info(inst->dev, "Reader state became idle, terminating smi transaction"); smi_disable_sync(inst->smi_inst); bcm2835_smi_set_regs_from_settings(inst->smi_inst); //dev_info(inst->dev, "Left reader thread"); inst->transfer_thread_running = false; inst->reader_waiting_sema = false; return ; } /**************************************************************************** * * FILE ops * ***************************************************************************/ static int smi_stream_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, "smi_stream_open: Unknown minor device: %d", dev); // error here return -ENXIO; } // create the data fifo ( N x dma_bounce size ) // we want this fifo to be deep enough to allow the application react without // loosing stream elements inst->rx_fifo_buffer = vmalloc(fifo_mtu_multiplier * DMA_BOUNCE_BUFFER_SIZE); if (!inst->rx_fifo_buffer) { printk(KERN_ERR DRIVER_NAME": error rx_fifo_buffer vmallok failed\n"); return -ENOMEM; } inst->tx_fifo_buffer = vmalloc(fifo_mtu_multiplier * DMA_BOUNCE_BUFFER_SIZE); if (!inst->tx_fifo_buffer) { printk(KERN_ERR DRIVER_NAME": error tx_fifo_buffer vmallok failed\n"); vfree(inst->rx_fifo_buffer); return -ENOMEM; } kfifo_init(&inst->rx_fifo, inst->rx_fifo_buffer, fifo_mtu_multiplier * DMA_BOUNCE_BUFFER_SIZE); kfifo_init(&inst->tx_fifo, inst->tx_fifo_buffer, fifo_mtu_multiplier * DMA_BOUNCE_BUFFER_SIZE); // when file is being openned, stream state is still idle set_state(smi_stream_idle); inst->address_changed = 0; return 0; } /***************************************************************************/ static int smi_stream_release(struct inode *inode, struct file *file) { int dev = iminor(inode); dev_info(inst->dev, "smi_stream_release: closing device: %d", dev); if (dev != DEVICE_MINOR) { dev_err(inst->dev, "smi_stream_release: Unknown minor device %d", dev); return -ENXIO; } // make sure stream is idle set_state(smi_stream_idle); if (inst->rx_fifo_buffer) vfree(inst->rx_fifo_buffer); if (inst->tx_fifo_buffer) vfree(inst->tx_fifo_buffer); inst->rx_fifo_buffer = NULL; inst->tx_fifo_buffer = NULL; inst->address_changed = 0; return 0; } /***************************************************************************/ static ssize_t smi_stream_read_file_fifo(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int ret = 0; unsigned int copied = 0; if (buf == NULL) { //dev_info(inst->dev, "Flushing internal rx_kfifo"); if (mutex_lock_interruptible(&inst->read_lock)) { return -EINTR; } kfifo_reset_out(&inst->rx_fifo); mutex_unlock(&inst->read_lock); inst->invalidate_rx_buffers = 1; return 0; } if (mutex_lock_interruptible(&inst->read_lock)) { return -EINTR; } ret = kfifo_to_user(&inst->rx_fifo, buf, count, &copied); mutex_unlock(&inst->read_lock); return ret < 0 ? ret : (ssize_t)copied; } /***************************************************************************/ static ssize_t smi_stream_write_file(struct file *f, const char __user *user_ptr, size_t count, loff_t *offs) { int ret = 0; unsigned int num_bytes_available = 0; unsigned int num_to_push = 0; unsigned int actual_copied = 0; if (mutex_lock_interruptible(&inst->write_lock)) { return -EAGAIN; } // check how many bytes are available in the tx fifo num_bytes_available = kfifo_avail(&inst->tx_fifo); num_to_push = num_bytes_available > count ? count : num_bytes_available; ret = kfifo_from_user(&inst->tx_fifo, user_ptr, num_to_push, &actual_copied); //dev_info(inst->dev, "smi_stream_write_file: pushed %ld bytes of %ld, available was %ld", actual_copied, count, num_bytes_available); mutex_unlock(&inst->write_lock); return ret ? ret : (ssize_t)actual_copied; } /***************************************************************************/ static unsigned int smi_stream_poll(struct file *filp, struct poll_table_struct *wait) { __poll_t mask = 0; poll_wait(filp, &inst->poll_event, wait); if (!kfifo_is_empty(&inst->rx_fifo)) { //dev_info(inst->dev, "poll_wait result => readable=%d", inst->readable); inst->readable = false; mask |= ( POLLIN | POLLRDNORM ); } if (!kfifo_is_full(&inst->rx_fifo)) { //dev_info(inst->dev, "poll_wait result => writeable=%d", inst->writeable); inst->writeable = false; mask |= ( POLLOUT | POLLWRNORM ); } return mask; } /***************************************************************************/ static const struct file_operations smi_stream_fops = { .owner = THIS_MODULE, .unlocked_ioctl = smi_stream_ioctl, .open = smi_stream_open, .release = smi_stream_release, .read = smi_stream_read_file_fifo, .write = smi_stream_write_file, .poll = smi_stream_poll, }; /**************************************************************************** * * smi_stream_probe - called when the driver is loaded. * ***************************************************************************/ static struct cdev smi_stream_cdev; static dev_t smi_stream_devid; static struct class *smi_stream_class; static struct device *smi_stream_dev; /***************************************************************************/ static int smi_stream_dev_probe(struct platform_device *pdev) { int err; void *ptr_err; struct device *dev = &pdev->dev; struct device_node *smi_node; printk(KERN_INFO DRIVER_NAME": smi_stream_dev_probe (fifo_mtu_multiplier=%d, addr_dir_offset=%d, addr_ch_offset=%d)\n", fifo_mtu_multiplier, addr_dir_offset, addr_ch_offset); // Check parameters if (fifo_mtu_multiplier > 32 || fifo_mtu_multiplier < 2) { dev_err(dev, "Parameter error: 2 4 || addr_dir_offset < -1) { dev_err(dev, "Parameter error: 0<=addr_dir_offset<=4 or (-1 - unused)"); return -EINVAL; } if (addr_ch_offset > 4 || addr_ch_offset < -1) { dev_err(dev, "Parameter error: 0<=addr_ch_offset<=4 or (-1 - unused)"); return -EINVAL; } if (addr_dir_offset == addr_ch_offset && addr_dir_offset != -1) { dev_err(dev, "Parameter error: addr_ch_offset should be different than addr_dir_offset"); return -EINVAL; } if (!dev->of_node) { dev_err(dev, "No device tree node supplied!"); return -EINVAL; } smi_node = of_parse_phandle(dev->of_node, "smi_handle", 0); if (!smi_node) { dev_err(dev, "No such property: smi_handle"); return -ENXIO; } // Allocate buffers and instance data (of type struct bcm2835_smi_dev_instance) inst = devm_kzalloc(dev, sizeof(*inst), GFP_KERNEL); if (!inst) { return -ENOMEM; } inst->smi_inst = bcm2835_smi_get(smi_node); if (!inst->smi_inst) { return -EPROBE_DEFER; } //smi_stream_print_smi_inst(inst->smi_inst); inst->dev = dev; /* Create character device entries */ err = alloc_chrdev_region(&smi_stream_devid, DEVICE_MINOR, 1, DEVICE_NAME); if (err != 0) { dev_err(inst->dev, "unable to allocate device number"); return -ENOMEM; } // init the char device with file operations cdev_init(&smi_stream_cdev, &smi_stream_fops); smi_stream_cdev.owner = THIS_MODULE; err = cdev_add(&smi_stream_cdev, smi_stream_devid, 1); if (err != 0) { dev_err(inst->dev, "unable to register device"); err = -ENOMEM; unregister_chrdev_region(smi_stream_devid, 1); dev_err(dev, "could not load smi_stream_dev"); return err; } // Create sysfs entries with "smi-stream-dev" smi_stream_class = class_create(THIS_MODULE, DEVICE_NAME); ptr_err = smi_stream_class; if (IS_ERR(ptr_err)) { cdev_del(&smi_stream_cdev); unregister_chrdev_region(smi_stream_devid, 1); dev_err(dev, "could not load smi_stream_dev"); return PTR_ERR(ptr_err); } printk(KERN_INFO DRIVER_NAME": creating a device and registering it with sysfs\n"); smi_stream_dev = device_create(smi_stream_class, // pointer to the struct class that this device should be registered to NULL, // pointer to the parent struct device of this new device, if any smi_stream_devid, // the dev_t for the char device to be added NULL, // the data to be added to the device for callbacks "smi"); // string for the device's name ptr_err = smi_stream_dev; if (IS_ERR(ptr_err)) { class_destroy(smi_stream_class); cdev_del(&smi_stream_cdev); unregister_chrdev_region(smi_stream_devid, 1); dev_err(dev, "could not load smi_stream_dev"); return PTR_ERR(ptr_err); } smi_setup_clock(inst->smi_inst); // Streaming instance initializations inst->invalidate_rx_buffers = 0; inst->invalidate_tx_buffers = 0; init_waitqueue_head(&inst->poll_event); inst->readable = false; inst->writeable = false; inst->transfer_thread_running = false; inst->reader_waiting_sema = false; inst->writer_waiting_sema = false; mutex_init(&inst->read_lock); mutex_init(&inst->write_lock); spin_lock_init(&inst->state_lock); dev_info(inst->dev, "initialised"); return 0; } /**************************************************************************** * * smi_stream_remove - called when the driver is unloaded. * ***************************************************************************/ static int smi_stream_dev_remove(struct platform_device *pdev) { //if (inst->reader_thread != NULL) kthread_stop(inst->reader_thread); //inst->reader_thread = NULL; device_destroy(smi_stream_class, smi_stream_devid); class_destroy(smi_stream_class); cdev_del(&smi_stream_cdev); unregister_chrdev_region(smi_stream_devid, 1); dev_info(inst->dev, DRIVER_NAME": smi-stream dev removed"); return 0; } /**************************************************************************** * * Register the driver with device tree * ***************************************************************************/ static const struct of_device_id smi_stream_dev_of_match[] = { {.compatible = "brcm,bcm2835-smi-dev",}, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, smi_stream_dev_of_match); static struct platform_driver smi_stream_dev_driver = { .probe = smi_stream_dev_probe, .remove = smi_stream_dev_remove, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = smi_stream_dev_of_match, }, }; module_platform_driver(smi_stream_dev_driver); //MODULE_INFO(intree, "Y"); MODULE_ALIAS("platform:smi-stream-dev"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Character device driver for BCM2835's secondary memory interface streaming mode"); MODULE_AUTHOR("David Michaeli ");