bug_fixes_integration_tx
David Michaeli 2021-11-24 16:02:32 +02:00
rodzic f9f673601c
commit 12e8d3e7de
10 zmienionych plików z 0 dodań i 2362 usunięć

Wyświetl plik

@ -1,955 +0,0 @@
/**
* Broadcom Secondary Memory Interface driver
*
* 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/clk.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/pagemap.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/semaphore.h>
#include <linux/spinlock.h>
#include <linux/io.h>
#define BCM2835_SMI_IMPLEMENTATION
#include <linux/broadcom/bcm2835_smi.h>
#define DRIVER_NAME "smi-bcm2835"
#define N_PAGES_FROM_BYTES(n) ((n + PAGE_SIZE-1) / PAGE_SIZE)
#define DMA_WRITE_TO_MEM true
#define DMA_READ_FROM_MEM false
struct bcm2835_smi_instance {
struct device *dev;
struct smi_settings settings;
__iomem void *smi_regs_ptr;
dma_addr_t smi_regs_busaddr;
struct dma_chan *dma_chan;
struct dma_slave_config dma_config;
struct bcm2835_smi_bounce_info bounce;
struct scatterlist buffer_sgl;
struct clk *clk;
/* Sometimes we are called into in an atomic context (e.g. by
JFFS2 + MTD) so we can't use a mutex */
spinlock_t transaction_lock;
};
/****************************************************************************
*
* SMI peripheral setup
*
***************************************************************************/
static inline void write_smi_reg(struct bcm2835_smi_instance *inst,
u32 val, unsigned reg)
{
writel(val, inst->smi_regs_ptr + reg);
}
static inline u32 read_smi_reg(struct bcm2835_smi_instance *inst, unsigned reg)
{
return readl(inst->smi_regs_ptr + reg);
}
/* Token-paste macro for e.g SMIDSR_RSTROBE -> value of SMIDSR_RSTROBE_MASK */
#define _CONCAT(x, y) x##y
#define CONCAT(x, y) _CONCAT(x, y)
#define SET_BIT_FIELD(dest, field, bits) ((dest) = \
((dest) & ~CONCAT(field, _MASK)) | (((bits) << CONCAT(field, _OFFS))& \
CONCAT(field, _MASK)))
#define GET_BIT_FIELD(src, field) (((src) & \
CONCAT(field, _MASK)) >> CONCAT(field, _OFFS))
static void smi_dump_context_labelled(struct bcm2835_smi_instance *inst,
const char *label)
{
dev_err(inst->dev, "SMI context dump: %s", label);
dev_err(inst->dev, "SMICS: 0x%08x", read_smi_reg(inst, SMICS));
dev_err(inst->dev, "SMIL: 0x%08x", read_smi_reg(inst, SMIL));
dev_err(inst->dev, "SMIDSR: 0x%08x", read_smi_reg(inst, SMIDSR0));
dev_err(inst->dev, "SMIDSW: 0x%08x", read_smi_reg(inst, SMIDSW0));
dev_err(inst->dev, "SMIDC: 0x%08x", read_smi_reg(inst, SMIDC));
dev_err(inst->dev, "SMIFD: 0x%08x", read_smi_reg(inst, SMIFD));
dev_err(inst->dev, " ");
}
static inline void smi_dump_context(struct bcm2835_smi_instance *inst)
{
smi_dump_context_labelled(inst, "");
}
static void smi_get_default_settings(struct bcm2835_smi_instance *inst)
{
struct smi_settings *settings = &inst->settings;
settings->data_width = SMI_WIDTH_16BIT;
settings->pack_data = true;
settings->read_setup_time = 1;
settings->read_hold_time = 1;
settings->read_pace_time = 1;
settings->read_strobe_time = 3;
settings->write_setup_time = settings->read_setup_time;
settings->write_hold_time = settings->read_hold_time;
settings->write_pace_time = settings->read_pace_time;
settings->write_strobe_time = settings->read_strobe_time;
settings->dma_enable = true;
settings->dma_passthrough_enable = false;
settings->dma_read_thresh = 0x01;
settings->dma_write_thresh = 0x3f;
settings->dma_panic_read_thresh = 0x20;
settings->dma_panic_write_thresh = 0x20;
}
void bcm2835_smi_set_regs_from_settings(struct bcm2835_smi_instance *inst)
{
struct smi_settings *settings = &inst->settings;
int smidsr_temp = 0, smidsw_temp = 0, smics_temp,
smidcs_temp, smidc_temp = 0;
spin_lock(&inst->transaction_lock);
/* temporarily disable the peripheral: */
smics_temp = read_smi_reg(inst, SMICS);
write_smi_reg(inst, 0, SMICS);
smidcs_temp = read_smi_reg(inst, SMIDCS);
write_smi_reg(inst, 0, SMIDCS);
if (settings->pack_data)
smics_temp |= SMICS_PXLDAT;
else
smics_temp &= ~SMICS_PXLDAT;
SET_BIT_FIELD(smidsr_temp, SMIDSR_RWIDTH, settings->data_width);
SET_BIT_FIELD(smidsr_temp, SMIDSR_RSETUP, settings->read_setup_time);
SET_BIT_FIELD(smidsr_temp, SMIDSR_RHOLD, settings->read_hold_time);
SET_BIT_FIELD(smidsr_temp, SMIDSR_RPACE, settings->read_pace_time);
SET_BIT_FIELD(smidsr_temp, SMIDSR_RSTROBE, settings->read_strobe_time);
write_smi_reg(inst, smidsr_temp, SMIDSR0);
SET_BIT_FIELD(smidsw_temp, SMIDSW_WWIDTH, settings->data_width);
if (settings->data_width == SMI_WIDTH_8BIT)
smidsw_temp |= SMIDSW_WSWAP;
else
smidsw_temp &= ~SMIDSW_WSWAP;
SET_BIT_FIELD(smidsw_temp, SMIDSW_WSETUP, settings->write_setup_time);
SET_BIT_FIELD(smidsw_temp, SMIDSW_WHOLD, settings->write_hold_time);
SET_BIT_FIELD(smidsw_temp, SMIDSW_WPACE, settings->write_pace_time);
SET_BIT_FIELD(smidsw_temp, SMIDSW_WSTROBE,
settings->write_strobe_time);
write_smi_reg(inst, smidsw_temp, SMIDSW0);
SET_BIT_FIELD(smidc_temp, SMIDC_REQR, settings->dma_read_thresh);
SET_BIT_FIELD(smidc_temp, SMIDC_REQW, settings->dma_write_thresh);
SET_BIT_FIELD(smidc_temp, SMIDC_PANICR,
settings->dma_panic_read_thresh);
SET_BIT_FIELD(smidc_temp, SMIDC_PANICW,
settings->dma_panic_write_thresh);
if (settings->dma_passthrough_enable) {
smidc_temp |= SMIDC_DMAP;
smidsr_temp |= SMIDSR_RDREQ;
write_smi_reg(inst, smidsr_temp, SMIDSR0);
smidsw_temp |= SMIDSW_WDREQ;
write_smi_reg(inst, smidsw_temp, SMIDSW0);
} else
smidc_temp &= ~SMIDC_DMAP;
if (settings->dma_enable)
smidc_temp |= SMIDC_DMAEN;
else
smidc_temp &= ~SMIDC_DMAEN;
write_smi_reg(inst, smidc_temp, SMIDC);
/* re-enable (if was previously enabled) */
write_smi_reg(inst, smics_temp, SMICS);
write_smi_reg(inst, smidcs_temp, SMIDCS);
spin_unlock(&inst->transaction_lock);
}
EXPORT_SYMBOL(bcm2835_smi_set_regs_from_settings);
struct smi_settings *bcm2835_smi_get_settings_from_regs
(struct bcm2835_smi_instance *inst)
{
struct smi_settings *settings = &inst->settings;
int smidsr, smidsw, smidc;
spin_lock(&inst->transaction_lock);
smidsr = read_smi_reg(inst, SMIDSR0);
smidsw = read_smi_reg(inst, SMIDSW0);
smidc = read_smi_reg(inst, SMIDC);
settings->pack_data = (read_smi_reg(inst, SMICS) & SMICS_PXLDAT) ?
true : false;
settings->data_width = GET_BIT_FIELD(smidsr, SMIDSR_RWIDTH);
settings->read_setup_time = GET_BIT_FIELD(smidsr, SMIDSR_RSETUP);
settings->read_hold_time = GET_BIT_FIELD(smidsr, SMIDSR_RHOLD);
settings->read_pace_time = GET_BIT_FIELD(smidsr, SMIDSR_RPACE);
settings->read_strobe_time = GET_BIT_FIELD(smidsr, SMIDSR_RSTROBE);
settings->write_setup_time = GET_BIT_FIELD(smidsw, SMIDSW_WSETUP);
settings->write_hold_time = GET_BIT_FIELD(smidsw, SMIDSW_WHOLD);
settings->write_pace_time = GET_BIT_FIELD(smidsw, SMIDSW_WPACE);
settings->write_strobe_time = GET_BIT_FIELD(smidsw, SMIDSW_WSTROBE);
settings->dma_read_thresh = GET_BIT_FIELD(smidc, SMIDC_REQR);
settings->dma_write_thresh = GET_BIT_FIELD(smidc, SMIDC_REQW);
settings->dma_panic_read_thresh = GET_BIT_FIELD(smidc, SMIDC_PANICR);
settings->dma_panic_write_thresh = GET_BIT_FIELD(smidc, SMIDC_PANICW);
settings->dma_passthrough_enable = (smidc & SMIDC_DMAP) ? true : false;
settings->dma_enable = (smidc & SMIDC_DMAEN) ? true : false;
spin_unlock(&inst->transaction_lock);
return settings;
}
EXPORT_SYMBOL(bcm2835_smi_get_settings_from_regs);
static inline void smi_set_address(struct bcm2835_smi_instance *inst,
unsigned int address)
{
int smia_temp = 0, smida_temp = 0;
SET_BIT_FIELD(smia_temp, SMIA_ADDR, address);
SET_BIT_FIELD(smida_temp, SMIDA_ADDR, address);
/* Write to both address registers - user doesn't care whether we're
doing programmed or direct transfers. */
write_smi_reg(inst, smia_temp, SMIA);
write_smi_reg(inst, smida_temp, SMIDA);
}
static void smi_setup_regs(struct bcm2835_smi_instance *inst)
{
dev_dbg(inst->dev, "Initialising SMI registers...");
/* Disable the peripheral if already enabled */
write_smi_reg(inst, 0, SMICS);
write_smi_reg(inst, 0, SMIDCS);
smi_get_default_settings(inst);
bcm2835_smi_set_regs_from_settings(inst);
smi_set_address(inst, 0);
write_smi_reg(inst, read_smi_reg(inst, SMICS) | SMICS_ENABLE, SMICS);
write_smi_reg(inst, read_smi_reg(inst, SMIDCS) | SMIDCS_ENABLE,
SMIDCS);
}
/****************************************************************************
*
* Low-level SMI access functions
* Other modules should use the exported higher-level functions e.g.
* bcm2835_smi_write_buf() unless they have a good reason to use these
*
***************************************************************************/
static inline uint32_t smi_read_single_word(struct bcm2835_smi_instance *inst)
{
int timeout = 0;
write_smi_reg(inst, SMIDCS_ENABLE, SMIDCS);
write_smi_reg(inst, SMIDCS_ENABLE | SMIDCS_START, SMIDCS);
/* Make sure things happen in the right order...*/
mb();
while (!(read_smi_reg(inst, SMIDCS) & SMIDCS_DONE) &&
++timeout < 10000)
;
if (timeout < 10000)
return read_smi_reg(inst, SMIDD);
dev_err(inst->dev,
"SMI direct read timed out (is the clock set up correctly?)");
return 0;
}
static inline void smi_write_single_word(struct bcm2835_smi_instance *inst,
uint32_t data)
{
int timeout = 0;
write_smi_reg(inst, SMIDCS_ENABLE | SMIDCS_WRITE, SMIDCS);
write_smi_reg(inst, data, SMIDD);
write_smi_reg(inst, SMIDCS_ENABLE | SMIDCS_WRITE | SMIDCS_START,
SMIDCS);
while (!(read_smi_reg(inst, SMIDCS) & SMIDCS_DONE) &&
++timeout < 10000)
;
if (timeout >= 10000)
dev_err(inst->dev,
"SMI direct write timed out (is the clock set up correctly?)");
}
/* Initiates a programmed read into the read FIFO. It is up to the caller to
* read data from the FIFO - either via paced DMA transfer,
* or polling SMICS_RXD to check whether data is available.
* SMICS_ACTIVE will go low upon completion. */
static void smi_init_programmed_read(struct bcm2835_smi_instance *inst,
int num_transfers)
{
int smics_temp;
/* Disable the peripheral: */
smics_temp = read_smi_reg(inst, SMICS) & ~(SMICS_ENABLE | SMICS_WRITE);
write_smi_reg(inst, smics_temp, SMICS);
while (read_smi_reg(inst, SMICS) & SMICS_ENABLE)
;
/* Program the transfer count: */
write_smi_reg(inst, num_transfers, SMIL);
/* re-enable and start: */
smics_temp |= SMICS_ENABLE;
write_smi_reg(inst, smics_temp, SMICS);
smics_temp |= SMICS_CLEAR;
/* Just to be certain: */
mb();
while (read_smi_reg(inst, SMICS) & SMICS_ACTIVE)
;
write_smi_reg(inst, smics_temp, SMICS);
smics_temp |= SMICS_START;
write_smi_reg(inst, smics_temp, SMICS);
}
/* Initiates a programmed write sequence, using data from the write FIFO.
* It is up to the caller to initiate a DMA transfer before calling,
* or use another method to keep the write FIFO topped up.
* SMICS_ACTIVE will go low upon completion.
*/
static void smi_init_programmed_write(struct bcm2835_smi_instance *inst,
int num_transfers)
{
int smics_temp;
/* Disable the peripheral: */
smics_temp = read_smi_reg(inst, SMICS) & ~SMICS_ENABLE;
write_smi_reg(inst, smics_temp, SMICS);
while (read_smi_reg(inst, SMICS) & SMICS_ENABLE)
;
/* Program the transfer count: */
write_smi_reg(inst, num_transfers, SMIL);
/* setup, re-enable and start: */
smics_temp |= SMICS_WRITE | SMICS_ENABLE;
write_smi_reg(inst, smics_temp, SMICS);
smics_temp |= SMICS_START;
write_smi_reg(inst, smics_temp, SMICS);
}
/* Initiate a read and then poll FIFO for data, reading out as it appears. */
static void smi_read_fifo(struct bcm2835_smi_instance *inst,
uint32_t *dest, int n_bytes)
{
if (read_smi_reg(inst, SMICS) & SMICS_RXD) {
smi_dump_context_labelled(inst,
"WARNING: read FIFO not empty at start of read call.");
while (read_smi_reg(inst, SMICS))
;
}
/* Dispatch the read: */
if (inst->settings.data_width == SMI_WIDTH_8BIT)
smi_init_programmed_read(inst, n_bytes);
else if (inst->settings.data_width == SMI_WIDTH_16BIT)
smi_init_programmed_read(inst, n_bytes / 2);
else {
dev_err(inst->dev, "Unsupported data width for read.");
return;
}
/* Poll FIFO to keep it empty */
while (!(read_smi_reg(inst, SMICS) & SMICS_DONE))
if (read_smi_reg(inst, SMICS) & SMICS_RXD)
*dest++ = read_smi_reg(inst, SMID);
/* Ensure that the FIFO is emptied */
if (read_smi_reg(inst, SMICS) & SMICS_RXD) {
int fifo_count;
fifo_count = GET_BIT_FIELD(read_smi_reg(inst, SMIFD),
SMIFD_FCNT);
while (fifo_count--)
*dest++ = read_smi_reg(inst, SMID);
}
if (!(read_smi_reg(inst, SMICS) & SMICS_DONE))
smi_dump_context_labelled(inst,
"WARNING: transaction finished but done bit not set.");
if (read_smi_reg(inst, SMICS) & SMICS_RXD)
smi_dump_context_labelled(inst,
"WARNING: read FIFO not empty at end of read call.");
}
/* Initiate a write, and then keep the FIFO topped up. */
static void smi_write_fifo(struct bcm2835_smi_instance *inst,
uint32_t *src, int n_bytes)
{
int i, timeout = 0;
/* Empty FIFOs if not already so */
if (!(read_smi_reg(inst, SMICS) & SMICS_TXE)) {
smi_dump_context_labelled(inst,
"WARNING: write fifo not empty at start of write call.");
write_smi_reg(inst, read_smi_reg(inst, SMICS) | SMICS_CLEAR,
SMICS);
}
/* Initiate the transfer */
if (inst->settings.data_width == SMI_WIDTH_8BIT)
smi_init_programmed_write(inst, n_bytes);
else if (inst->settings.data_width == SMI_WIDTH_16BIT)
smi_init_programmed_write(inst, n_bytes / 2);
else {
dev_err(inst->dev, "Unsupported data width for write.");
return;
}
/* Fill the FIFO: */
for (i = 0; i < (n_bytes - 1) / 4 + 1; ++i) {
while (!(read_smi_reg(inst, SMICS) & SMICS_TXD))
;
write_smi_reg(inst, *src++, SMID);
}
/* Busy wait... */
while (!(read_smi_reg(inst, SMICS) & SMICS_DONE) && ++timeout <
1000000)
;
if (timeout >= 1000000)
smi_dump_context_labelled(inst,
"Timed out on write operation!");
if (!(read_smi_reg(inst, SMICS) & SMICS_TXE))
smi_dump_context_labelled(inst,
"WARNING: FIFO not empty at end of write operation.");
}
/****************************************************************************
*
* SMI DMA operations
*
***************************************************************************/
/* Disable SMI and put it into the correct direction before doing DMA setup.
Stops spurious DREQs during setup. Peripheral is re-enabled by init_*() */
static void smi_disable(struct bcm2835_smi_instance *inst,
enum dma_transfer_direction direction)
{
int smics_temp = read_smi_reg(inst, SMICS) & ~SMICS_ENABLE;
if (direction == DMA_DEV_TO_MEM)
smics_temp &= ~SMICS_WRITE;
else
smics_temp |= SMICS_WRITE;
write_smi_reg(inst, smics_temp, SMICS);
while (read_smi_reg(inst, SMICS) & SMICS_ACTIVE)
;
}
static struct scatterlist *smi_scatterlist_from_buffer(
struct bcm2835_smi_instance *inst,
dma_addr_t buf,
size_t len,
struct scatterlist *sg)
{
sg_init_table(sg, 1);
sg_dma_address(sg) = buf;
sg_dma_len(sg) = len;
return sg;
}
static void smi_dma_callback_user_copy(void *param)
{
/* Notify the bottom half that a chunk is ready for user copy */
struct bcm2835_smi_instance *inst =
(struct bcm2835_smi_instance *)param;
up(&inst->bounce.callback_sem);
}
/* Creates a descriptor, assigns the given callback, and submits the
descriptor to dmaengine. Does not block - can queue up multiple
descriptors and then wait for them all to complete.
sg_len is the number of control blocks, NOT the number of bytes.
dir can be DMA_MEM_TO_DEV or DMA_DEV_TO_MEM.
callback can be NULL - in this case it is not called. */
static inline struct dma_async_tx_descriptor *smi_dma_submit_sgl(
struct bcm2835_smi_instance *inst,
struct scatterlist *sgl,
size_t sg_len,
enum dma_transfer_direction dir,
dma_async_tx_callback callback)
{
struct dma_async_tx_descriptor *desc;
desc = dmaengine_prep_slave_sg(inst->dma_chan,
sgl,
sg_len,
dir,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK |
DMA_PREP_FENCE);
if (!desc) {
dev_err(inst->dev, "read_sgl: dma slave preparation failed!");
write_smi_reg(inst, read_smi_reg(inst, SMICS) & ~SMICS_ACTIVE,
SMICS);
while (read_smi_reg(inst, SMICS) & SMICS_ACTIVE)
cpu_relax();
write_smi_reg(inst, read_smi_reg(inst, SMICS) | SMICS_ACTIVE,
SMICS);
return NULL;
}
desc->callback = callback;
desc->callback_param = inst;
if (dmaengine_submit(desc) < 0)
return NULL;
return desc;
}
/* NB this function blocks until the transfer is complete */
static void
smi_dma_read_sgl(struct bcm2835_smi_instance *inst,
struct scatterlist *sgl, size_t sg_len, size_t n_bytes)
{
struct dma_async_tx_descriptor *desc;
/* Disable SMI and set to read before dispatching DMA - if SMI is in
* write mode and TX fifo is empty, it will generate a DREQ which may
* cause the read DMA to complete before the SMI read command is even
* dispatched! We want to dispatch DMA before SMI read so that reading
* is gapless, for logic analyser.
*/
smi_disable(inst, DMA_DEV_TO_MEM);
desc = smi_dma_submit_sgl(inst, sgl, sg_len, DMA_DEV_TO_MEM, NULL);
dma_async_issue_pending(inst->dma_chan);
if (inst->settings.data_width == SMI_WIDTH_8BIT)
smi_init_programmed_read(inst, n_bytes);
else
smi_init_programmed_read(inst, n_bytes / 2);
if (dma_wait_for_async_tx(desc) == DMA_ERROR)
smi_dump_context_labelled(inst, "DMA timeout!");
}
static void
smi_dma_write_sgl(struct bcm2835_smi_instance *inst,
struct scatterlist *sgl, size_t sg_len, size_t n_bytes)
{
struct dma_async_tx_descriptor *desc;
if (inst->settings.data_width == SMI_WIDTH_8BIT)
smi_init_programmed_write(inst, n_bytes);
else
smi_init_programmed_write(inst, n_bytes / 2);
desc = smi_dma_submit_sgl(inst, sgl, sg_len, DMA_MEM_TO_DEV, NULL);
dma_async_issue_pending(inst->dma_chan);
if (dma_wait_for_async_tx(desc) == DMA_ERROR)
smi_dump_context_labelled(inst, "DMA timeout!");
else
/* Wait for SMI to finish our writes */
while (!(read_smi_reg(inst, SMICS) & SMICS_DONE))
cpu_relax();
}
ssize_t bcm2835_smi_user_dma(
struct bcm2835_smi_instance *inst,
enum dma_transfer_direction dma_dir,
char __user *user_ptr, size_t count,
struct bcm2835_smi_bounce_info **bounce)
{
int chunk_no = 0, chunk_size, count_left = count;
struct scatterlist *sgl;
void (*init_trans_func)(struct bcm2835_smi_instance *, int);
spin_lock(&inst->transaction_lock);
if (dma_dir == DMA_DEV_TO_MEM)
init_trans_func = smi_init_programmed_read;
else
init_trans_func = smi_init_programmed_write;
smi_disable(inst, dma_dir);
sema_init(&inst->bounce.callback_sem, 0);
if (bounce)
*bounce = &inst->bounce;
while (count_left) {
chunk_size = count_left > DMA_BOUNCE_BUFFER_SIZE ?
DMA_BOUNCE_BUFFER_SIZE : count_left;
if (chunk_size == DMA_BOUNCE_BUFFER_SIZE) {
sgl =
&inst->bounce.sgl[chunk_no % DMA_BOUNCE_BUFFER_COUNT];
} else {
sgl = smi_scatterlist_from_buffer(
inst,
inst->bounce.phys[
chunk_no % DMA_BOUNCE_BUFFER_COUNT],
chunk_size,
&inst->buffer_sgl);
}
if (!smi_dma_submit_sgl(inst, sgl, 1, dma_dir,
smi_dma_callback_user_copy
)) {
dev_err(inst->dev, "sgl submit failed");
count = 0;
goto out;
}
count_left -= chunk_size;
chunk_no++;
}
dma_async_issue_pending(inst->dma_chan);
if (inst->settings.data_width == SMI_WIDTH_8BIT)
init_trans_func(inst, count);
else if (inst->settings.data_width == SMI_WIDTH_16BIT)
init_trans_func(inst, count / 2);
out:
spin_unlock(&inst->transaction_lock);
return count;
}
EXPORT_SYMBOL(bcm2835_smi_user_dma);
/****************************************************************************
*
* High level buffer transfer functions - for use by other drivers
*
***************************************************************************/
/* Buffer must be physically contiguous - i.e. kmalloc, not vmalloc! */
void bcm2835_smi_write_buf(
struct bcm2835_smi_instance *inst,
const void *buf, size_t n_bytes)
{
int odd_bytes = n_bytes & 0x3;
n_bytes -= odd_bytes;
spin_lock(&inst->transaction_lock);
if (n_bytes > DMA_THRESHOLD_BYTES) {
dma_addr_t phy_addr = dma_map_single(
inst->dev,
(void *)buf,
n_bytes,
DMA_MEM_TO_DEV);
struct scatterlist *sgl =
smi_scatterlist_from_buffer(inst, phy_addr, n_bytes,
&inst->buffer_sgl);
if (!sgl) {
smi_dump_context_labelled(inst,
"Error: could not create scatterlist for write!");
goto out;
}
smi_dma_write_sgl(inst, sgl, 1, n_bytes);
dma_unmap_single
(inst->dev, phy_addr, n_bytes, DMA_MEM_TO_DEV);
} else if (n_bytes) {
smi_write_fifo(inst, (uint32_t *) buf, n_bytes);
}
buf += n_bytes;
if (inst->settings.data_width == SMI_WIDTH_8BIT) {
while (odd_bytes--)
smi_write_single_word(inst, *(uint8_t *) (buf++));
} else {
while (odd_bytes >= 2) {
smi_write_single_word(inst, *(uint16_t *)buf);
buf += 2;
odd_bytes -= 2;
}
if (odd_bytes) {
/* Reading an odd number of bytes on a 16 bit bus is
a user bug. It's kinder to fail early and tell them
than to e.g. transparently give them the bottom byte
of a 16 bit transfer. */
dev_err(inst->dev,
"WARNING: odd number of bytes specified for wide transfer.");
dev_err(inst->dev,
"At least one byte dropped as a result.");
dump_stack();
}
}
out:
spin_unlock(&inst->transaction_lock);
}
EXPORT_SYMBOL(bcm2835_smi_write_buf);
void bcm2835_smi_read_buf(struct bcm2835_smi_instance *inst,
void *buf, size_t n_bytes)
{
/* SMI is inherently 32-bit, which causes surprising amounts of mess
for bytes % 4 != 0. Easiest to avoid this mess altogether
by handling remainder separately. */
int odd_bytes = n_bytes & 0x3;
spin_lock(&inst->transaction_lock);
n_bytes -= odd_bytes;
if (n_bytes > DMA_THRESHOLD_BYTES) {
dma_addr_t phy_addr = dma_map_single(inst->dev,
buf, n_bytes,
DMA_DEV_TO_MEM);
struct scatterlist *sgl = smi_scatterlist_from_buffer(
inst, phy_addr, n_bytes,
&inst->buffer_sgl);
if (!sgl) {
smi_dump_context_labelled(inst,
"Error: could not create scatterlist for read!");
goto out;
}
smi_dma_read_sgl(inst, sgl, 1, n_bytes);
dma_unmap_single(inst->dev, phy_addr, n_bytes, DMA_DEV_TO_MEM);
} else if (n_bytes) {
smi_read_fifo(inst, (uint32_t *)buf, n_bytes);
}
buf += n_bytes;
if (inst->settings.data_width == SMI_WIDTH_8BIT) {
while (odd_bytes--)
*((uint8_t *) (buf++)) = smi_read_single_word(inst);
} else {
while (odd_bytes >= 2) {
*(uint16_t *) buf = smi_read_single_word(inst);
buf += 2;
odd_bytes -= 2;
}
if (odd_bytes) {
dev_err(inst->dev,
"WARNING: odd number of bytes specified for wide transfer.");
dev_err(inst->dev,
"At least one byte dropped as a result.");
dump_stack();
}
}
out:
spin_unlock(&inst->transaction_lock);
}
EXPORT_SYMBOL(bcm2835_smi_read_buf);
void bcm2835_smi_set_address(struct bcm2835_smi_instance *inst,
unsigned int address)
{
spin_lock(&inst->transaction_lock);
smi_set_address(inst, address);
spin_unlock(&inst->transaction_lock);
}
EXPORT_SYMBOL(bcm2835_smi_set_address);
struct bcm2835_smi_instance *bcm2835_smi_get(struct device_node *node)
{
struct platform_device *pdev;
if (!node)
return NULL;
pdev = of_find_device_by_node(node);
if (!pdev)
return NULL;
return platform_get_drvdata(pdev);
}
EXPORT_SYMBOL(bcm2835_smi_get);
/****************************************************************************
*
* bcm2835_smi_probe - called when the driver is loaded.
*
***************************************************************************/
static int bcm2835_smi_dma_setup(struct bcm2835_smi_instance *inst)
{
int i, rv = 0;
inst->dma_chan = dma_request_slave_channel(inst->dev, "rx-tx");
inst->dma_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
inst->dma_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
inst->dma_config.src_addr = inst->smi_regs_busaddr + SMID;
inst->dma_config.dst_addr = inst->dma_config.src_addr;
/* Direction unimportant - always overridden by prep_slave_sg */
inst->dma_config.direction = DMA_DEV_TO_MEM;
dmaengine_slave_config(inst->dma_chan, &inst->dma_config);
/* Alloc and map bounce buffers */
for (i = 0; i < DMA_BOUNCE_BUFFER_COUNT; ++i) {
inst->bounce.buffer[i] =
dmam_alloc_coherent(inst->dev, DMA_BOUNCE_BUFFER_SIZE,
&inst->bounce.phys[i],
GFP_KERNEL);
if (!inst->bounce.buffer[i]) {
dev_err(inst->dev, "Could not allocate buffer!");
rv = -ENOMEM;
break;
}
smi_scatterlist_from_buffer(
inst,
inst->bounce.phys[i],
DMA_BOUNCE_BUFFER_SIZE,
&inst->bounce.sgl[i]
);
}
return rv;
}
static int bcm2835_smi_probe(struct platform_device *pdev)
{
int err;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct resource *ioresource;
struct bcm2835_smi_instance *inst;
const __be32 *addr;
/* We require device tree support */
if (!node)
return -EINVAL;
/* Allocate buffers and instance data */
inst = devm_kzalloc(dev, sizeof(struct bcm2835_smi_instance),
GFP_KERNEL);
if (!inst)
return -ENOMEM;
inst->dev = dev;
spin_lock_init(&inst->transaction_lock);
ioresource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
inst->smi_regs_ptr = devm_ioremap_resource(dev, ioresource);
if (IS_ERR(inst->smi_regs_ptr)) {
err = PTR_ERR(inst->smi_regs_ptr);
goto err;
}
addr = of_get_address(node, 0, NULL, NULL);
inst->smi_regs_busaddr = be32_to_cpu(*addr);
err = bcm2835_smi_dma_setup(inst);
if (err)
goto err;
/* request clock */
inst->clk = devm_clk_get(dev, NULL);
if (!inst->clk)
goto err;
clk_prepare_enable(inst->clk);
/* Finally, do peripheral setup */
smi_setup_regs(inst);
platform_set_drvdata(pdev, inst);
dev_info(inst->dev, "initialised");
return 0;
err:
kfree(inst);
return err;
}
/****************************************************************************
*
* bcm2835_smi_remove - called when the driver is unloaded.
*
***************************************************************************/
static int bcm2835_smi_remove(struct platform_device *pdev)
{
struct bcm2835_smi_instance *inst = platform_get_drvdata(pdev);
struct device *dev = inst->dev;
dmaengine_terminate_all(inst->dma_chan);
dma_release_channel(inst->dma_chan);
clk_disable_unprepare(inst->clk);
dev_info(dev, "SMI device removed - OK");
return 0;
}
/****************************************************************************
*
* Register the driver with device tree
*
***************************************************************************/
static const struct of_device_id bcm2835_smi_of_match[] = {
{.compatible = "brcm,bcm2835-smi",},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, bcm2835_smi_of_match);
static struct platform_driver bcm2835_smi_driver = {
.probe = bcm2835_smi_probe,
.remove = bcm2835_smi_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = bcm2835_smi_of_match,
},
};
module_platform_driver(bcm2835_smi_driver);
MODULE_ALIAS("platform:smi-bcm2835");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Device driver for BCM2835's secondary memory interface");
MODULE_AUTHOR("Luke Wren <luke@raspberrypi.org>");

Wyświetl plik

@ -1,391 +0,0 @@
/**
* Declarations and definitions for Broadcom's Secondary Memory Interface
*
* Written by Luke Wren <luke@raspberrypi.org>
* Copyright (c) 2015, Raspberry Pi (Trading) Ltd.
* Copyright (c) 2010-2012 Broadcom. All rights reserved.
*
* 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.
*/
#ifndef BCM2835_SMI_H
#define BCM2835_SMI_H
#include <linux/ioctl.h>
#ifndef __KERNEL__
#include <stdint.h>
#include <stdbool.h>
#endif
#define BCM2835_SMI_IOC_MAGIC 0x1
#define BCM2835_SMI_INVALID_HANDLE (~0)
/* IOCTLs 0x100...0x1ff are not device-specific - we can use them */
#define BCM2835_SMI_IOC_GET_SETTINGS _IO(BCM2835_SMI_IOC_MAGIC, 0)
#define BCM2835_SMI_IOC_WRITE_SETTINGS _IO(BCM2835_SMI_IOC_MAGIC, 1)
#define BCM2835_SMI_IOC_ADDRESS _IO(BCM2835_SMI_IOC_MAGIC, 2)
#define BCM2835_SMI_IOC_MAX 2
#define SMI_WIDTH_8BIT 0
#define SMI_WIDTH_16BIT 1
#define SMI_WIDTH_9BIT 2
#define SMI_WIDTH_18BIT 3
/* max number of bytes where DMA will not be used */
#define DMA_THRESHOLD_BYTES 128
#define DMA_BOUNCE_BUFFER_SIZE (1024 * 1024 / 2)
#define DMA_BOUNCE_BUFFER_COUNT 3
struct smi_settings {
int data_width;
/* Whether or not to pack multiple SMI transfers into a
single 32 bit FIFO word */
bool pack_data;
/* Timing for reads (writes the same but for WE)
*
* OE ----------+ +--------------------
* | |
* +----------+
* SD -<==============================>-----------
* SA -<=========================================>-
* <-setup-> <-strobe -> <-hold -> <- pace ->
*/
int read_setup_time;
int read_hold_time;
int read_pace_time;
int read_strobe_time;
int write_setup_time;
int write_hold_time;
int write_pace_time;
int write_strobe_time;
bool dma_enable; /* DREQs */
bool dma_passthrough_enable; /* External DREQs */
int dma_read_thresh;
int dma_write_thresh;
int dma_panic_read_thresh;
int dma_panic_write_thresh;
};
/****************************************************************************
*
* Declare exported SMI functions
*
***************************************************************************/
#ifdef __KERNEL__
#include <linux/dmaengine.h> /* for enum dma_transfer_direction */
#include <linux/of.h>
#include <linux/semaphore.h>
struct bcm2835_smi_instance;
struct bcm2835_smi_bounce_info {
struct semaphore callback_sem;
void *buffer[DMA_BOUNCE_BUFFER_COUNT];
dma_addr_t phys[DMA_BOUNCE_BUFFER_COUNT];
struct scatterlist sgl[DMA_BOUNCE_BUFFER_COUNT];
};
void bcm2835_smi_set_regs_from_settings(struct bcm2835_smi_instance *);
struct smi_settings *bcm2835_smi_get_settings_from_regs(
struct bcm2835_smi_instance *inst);
void bcm2835_smi_write_buf(
struct bcm2835_smi_instance *inst,
const void *buf,
size_t n_bytes);
void bcm2835_smi_read_buf(
struct bcm2835_smi_instance *inst,
void *buf,
size_t n_bytes);
void bcm2835_smi_set_address(struct bcm2835_smi_instance *inst,
unsigned int address);
ssize_t bcm2835_smi_user_dma(
struct bcm2835_smi_instance *inst,
enum dma_transfer_direction dma_dir,
char __user *user_ptr,
size_t count,
struct bcm2835_smi_bounce_info **bounce);
struct bcm2835_smi_instance *bcm2835_smi_get(struct device_node *node);
#endif /* __KERNEL__ */
/****************************************************************
*
* Implementation-only declarations
*
****************************************************************/
#ifdef BCM2835_SMI_IMPLEMENTATION
/* Clock manager registers for SMI clock: */
#define CM_SMI_BASE_ADDRESS ((BCM2708_PERI_BASE) + 0x1010b0)
/* Clock manager "password" to protect registers from spurious writes */
#define CM_PWD (0x5a << 24)
#define CM_SMI_CTL 0x00
#define CM_SMI_DIV 0x04
#define CM_SMI_CTL_FLIP (1 << 8)
#define CM_SMI_CTL_BUSY (1 << 7)
#define CM_SMI_CTL_KILL (1 << 5)
#define CM_SMI_CTL_ENAB (1 << 4)
#define CM_SMI_CTL_SRC_MASK (0xf)
#define CM_SMI_CTL_SRC_OFFS (0)
#define CM_SMI_DIV_DIVI_MASK (0xf << 12)
#define CM_SMI_DIV_DIVI_OFFS (12)
#define CM_SMI_DIV_DIVF_MASK (0xff << 4)
#define CM_SMI_DIV_DIVF_OFFS (4)
/* SMI register mapping:*/
#define SMI_BASE_ADDRESS ((BCM2708_PERI_BASE) + 0x600000)
#define SMICS 0x00 /* control + status register */
#define SMIL 0x04 /* length/count (n external txfers) */
#define SMIA 0x08 /* address register */
#define SMID 0x0c /* data register */
#define SMIDSR0 0x10 /* device 0 read settings */
#define SMIDSW0 0x14 /* device 0 write settings */
#define SMIDSR1 0x18 /* device 1 read settings */
#define SMIDSW1 0x1c /* device 1 write settings */
#define SMIDSR2 0x20 /* device 2 read settings */
#define SMIDSW2 0x24 /* device 2 write settings */
#define SMIDSR3 0x28 /* device 3 read settings */
#define SMIDSW3 0x2c /* device 3 write settings */
#define SMIDC 0x30 /* DMA control registers */
#define SMIDCS 0x34 /* direct control/status register */
#define SMIDA 0x38 /* direct address register */
#define SMIDD 0x3c /* direct data registers */
#define SMIFD 0x40 /* FIFO debug register */
/* Control and Status register bits:
* SMICS_RXF : RX fifo full: 1 when RX fifo is full
* SMICS_TXE : TX fifo empty: 1 when empty.
* SMICS_RXD : RX fifo contains data: 1 when there is data.
* SMICS_TXD : TX fifo can accept data: 1 when true.
* SMICS_RXR : RX fifo needs reading: 1 when fifo more than 3/4 full, or
* when "DONE" and fifo not emptied.
* SMICS_TXW : TX fifo needs writing: 1 when less than 1/4 full.
* SMICS_AFERR : AXI FIFO error: 1 when fifo read when empty or written
* when full. Write 1 to clear.
* SMICS_EDREQ : 1 when external DREQ received.
* SMICS_PXLDAT : Pixel data: write 1 to enable pixel transfer modes.
* SMICS_SETERR : 1 if there was an error writing to setup regs (e.g.
* tx was in progress). Write 1 to clear.
* SMICS_PVMODE : Set to 1 to enable pixel valve mode.
* SMICS_INTR : Set to 1 to enable interrupt on RX.
* SMICS_INTT : Set to 1 to enable interrupt on TX.
* SMICS_INTD : Set to 1 to enable interrupt on DONE condition.
* SMICS_TEEN : Tear effect mode enabled: Programmed transfers will wait
* for a TE trigger before writing.
* SMICS_PAD1 : Padding settings for external transfers. For writes: the
* number of bytes initially written to the TX fifo that
* SMICS_PAD0 : should be ignored. For reads: the number of bytes that will
* be read before the data, and should be dropped.
* SMICS_WRITE : Transfer direction: 1 = write to external device, 0 = read
* SMICS_CLEAR : Write 1 to clear the FIFOs.
* SMICS_START : Write 1 to start the programmed transfer.
* SMICS_ACTIVE : Reads as 1 when a programmed transfer is underway.
* SMICS_DONE : Reads as 1 when transfer finished. For RX, not set until
* FIFO emptied.
* SMICS_ENABLE : Set to 1 to enable the SMI peripheral, 0 to disable.
*/
#define SMICS_RXF (1 << 31)
#define SMICS_TXE (1 << 30)
#define SMICS_RXD (1 << 29)
#define SMICS_TXD (1 << 28)
#define SMICS_RXR (1 << 27)
#define SMICS_TXW (1 << 26)
#define SMICS_AFERR (1 << 25)
#define SMICS_EDREQ (1 << 15)
#define SMICS_PXLDAT (1 << 14)
#define SMICS_SETERR (1 << 13)
#define SMICS_PVMODE (1 << 12)
#define SMICS_INTR (1 << 11)
#define SMICS_INTT (1 << 10)
#define SMICS_INTD (1 << 9)
#define SMICS_TEEN (1 << 8)
#define SMICS_PAD1 (1 << 7)
#define SMICS_PAD0 (1 << 6)
#define SMICS_WRITE (1 << 5)
#define SMICS_CLEAR (1 << 4)
#define SMICS_START (1 << 3)
#define SMICS_ACTIVE (1 << 2)
#define SMICS_DONE (1 << 1)
#define SMICS_ENABLE (1 << 0)
/* Address register bits: */
#define SMIA_DEVICE_MASK ((1 << 9) | (1 << 8))
#define SMIA_DEVICE_OFFS (8)
#define SMIA_ADDR_MASK (0x3f) /* bits 5 -> 0 */
#define SMIA_ADDR_OFFS (0)
/* DMA control register bits:
* SMIDC_DMAEN : DMA enable: set 1: DMA requests will be issued.
* SMIDC_DMAP : DMA passthrough: when set to 0, top two data pins are used by
* SMI as usual. When set to 1, the top two pins are used for
* external DREQs: pin 16 read request, 17 write.
* SMIDC_PANIC* : Threshold at which DMA will panic during read/write.
* SMIDC_REQ* : Threshold at which DMA will generate a DREQ.
*/
#define SMIDC_DMAEN (1 << 28)
#define SMIDC_DMAP (1 << 24)
#define SMIDC_PANICR_MASK (0x3f << 18)
#define SMIDC_PANICR_OFFS (18)
#define SMIDC_PANICW_MASK (0x3f << 12)
#define SMIDC_PANICW_OFFS (12)
#define SMIDC_REQR_MASK (0x3f << 6)
#define SMIDC_REQR_OFFS (6)
#define SMIDC_REQW_MASK (0x3f)
#define SMIDC_REQW_OFFS (0)
/* Device settings register bits: same for all 4 (or 3?) device register sets.
* Device read settings:
* SMIDSR_RWIDTH : Read transfer width. 00 = 8bit, 01 = 16bit,
* 10 = 18bit, 11 = 9bit.
* SMIDSR_RSETUP : Read setup time: number of core cycles between chip
* select/address and read strobe. Min 1, max 64.
* SMIDSR_MODE68 : 1 for System 68 mode (i.e. enable + direction pins,
* rather than OE + WE pin)
* SMIDSR_FSETUP : If set to 1, setup time only applies to first
* transfer after address change.
* SMIDSR_RHOLD : Number of core cycles between read strobe going
* inactive and CS/address going inactive. Min 1, max 64
* SMIDSR_RPACEALL : When set to 1, this device's RPACE value will always
* be used for the next transaction, even if it is not
* to this device.
* SMIDSR_RPACE : Number of core cycles spent waiting between CS
* deassert and start of next transfer. Min 1, max 128
* SMIDSR_RDREQ : 1 = use external DMA request on SD16 to pace reads
* from device. Must also set DMAP in SMICS.
* SMIDSR_RSTROBE : Number of cycles to assert the read strobe.
* min 1, max 128.
*/
#define SMIDSR_RWIDTH_MASK ((1<<31)|(1<<30))
#define SMIDSR_RWIDTH_OFFS (30)
#define SMIDSR_RSETUP_MASK (0x3f << 24)
#define SMIDSR_RSETUP_OFFS (24)
#define SMIDSR_MODE68 (1 << 23)
#define SMIDSR_FSETUP (1 << 22)
#define SMIDSR_RHOLD_MASK (0x3f << 16)
#define SMIDSR_RHOLD_OFFS (16)
#define SMIDSR_RPACEALL (1 << 15)
#define SMIDSR_RPACE_MASK (0x7f << 8)
#define SMIDSR_RPACE_OFFS (8)
#define SMIDSR_RDREQ (1 << 7)
#define SMIDSR_RSTROBE_MASK (0x7f)
#define SMIDSR_RSTROBE_OFFS (0)
/* Device write settings:
* SMIDSW_WWIDTH : Write transfer width. 00 = 8bit, 01 = 16bit,
* 10= 18bit, 11 = 9bit.
* SMIDSW_WSETUP : Number of cycles between CS assert and write strobe.
* Min 1, max 64.
* SMIDSW_WFORMAT : Pixel format of input. 0 = 16bit RGB 565,
* 1 = 32bit RGBA 8888
* SMIDSW_WSWAP : 1 = swap pixel data bits. (Use with SMICS_PXLDAT)
* SMIDSW_WHOLD : Time between WE deassert and CS deassert. 1 to 64
* SMIDSW_WPACEALL : 1: this device's WPACE will be used for the next
* transfer, regardless of that transfer's device.
* SMIDSW_WPACE : Cycles between CS deassert and next CS assert.
* Min 1, max 128
* SMIDSW_WDREQ : Use external DREQ on pin 17 to pace writes. DMAP must
* be set in SMICS.
* SMIDSW_WSTROBE : Number of cycles to assert the write strobe.
* Min 1, max 128
*/
#define SMIDSW_WWIDTH_MASK ((1<<31)|(1<<30))
#define SMIDSW_WWIDTH_OFFS (30)
#define SMIDSW_WSETUP_MASK (0x3f << 24)DMA_THRESHOLD_BYTES
#define SMIDSW_WSETUP_OFFS (24)
#define SMIDSW_WFORMAT (1 << 23)
#define SMIDSW_WSWAP (1 << 22)
#define SMIDSW_WHOLD_MASK (0x3f << 16)
#define SMIDSW_WHOLD_OFFS (16)
#define SMIDSW_WPACEALL (1 << 15)
#define SMIDSW_WPACE_MASK (0x7f << 8)
#define SMIDSW_WPACE_OFFS (8)
#define SMIDSW_WDREQ (1 << 7)
#define SMIDSW_WSTROBE_MASK (0x7f)
#define SMIDSW_WSTROBE_OFFS (0)
/* Direct transfer control + status register
* SMIDCS_WRITE : Direction of transfer: 1 -> write, 0 -> read
* SMIDCS_DONE : 1 when a transfer has finished. Write 1 to clear.
* SMIDCS_START : Write 1 to start a transfer, if one is not already underway.
* SMIDCE_ENABLE: Write 1 to enable SMI in direct mode.
*/
#define SMIDCS_WRITE (1 << 3)
#define SMIDCS_DONE (1 << 2)
#define SMIDCS_START (1 << 1)
#define SMIDCS_ENABLE (1 << 0)
/* Direct transfer address register
* SMIDA_DEVICE : Indicates which of the device settings banks should be used.
* SMIDA_ADDR : The value to be asserted on the address pins.
*/
#define SMIDA_DEVICE_MASK ((1<<9)|(1<<8))
#define SMIDA_DEVICE_OFFS (8)
#define SMIDA_ADDR_MASK (0x3f)
#define SMIDA_ADDR_OFFS (0)
/* FIFO debug register
* SMIFD_FLVL : The high-tide mark of FIFO count during the most recent txfer
* SMIFD_FCNT : The current FIFO count.
*/
#define SMIFD_FLVL_MASK (0x3f << 8)
#define SMIFD_FLVL_OFFS (8)
#define SMIFD_FCNT_MASK (0x3f)
#define SMIFD_FCNT_OFFS (0)
#endif /* BCM2835_SMI_IMPLEMENTATION */
#endif /* BCM2835_SMI_H */

Wyświetl plik

@ -1,402 +0,0 @@
/**
* 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>");

Wyświetl plik

@ -1,268 +0,0 @@
/**
* NAND flash 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/mtd/nand.h>
#include <linux/mtd/partitions.h>
#include <linux/broadcom/bcm2835_smi.h>
#define DEVICE_NAME "bcm2835-smi-nand"
#define DRIVER_NAME "smi-nand-bcm2835"
struct bcm2835_smi_nand_host {
struct bcm2835_smi_instance *smi_inst;
struct nand_chip nand_chip;
struct mtd_info mtd;
struct device *dev;
};
/****************************************************************************
*
* NAND functionality implementation
*
****************************************************************************/
#define SMI_NAND_CLE_PIN 0x01
#define SMI_NAND_ALE_PIN 0x02
static inline void bcm2835_smi_nand_cmd_ctrl(struct mtd_info *mtd, int cmd,
unsigned int ctrl)
{
uint32_t cmd32 = cmd;
uint32_t addr = ~(SMI_NAND_CLE_PIN | SMI_NAND_ALE_PIN);
struct bcm2835_smi_nand_host *host = dev_get_drvdata(mtd->dev.parent);
struct bcm2835_smi_instance *inst = host->smi_inst;
if (ctrl & NAND_CLE)
addr |= SMI_NAND_CLE_PIN;
if (ctrl & NAND_ALE)
addr |= SMI_NAND_ALE_PIN;
/* Lower ALL the CS pins! */
if (ctrl & NAND_NCE)
addr &= (SMI_NAND_CLE_PIN | SMI_NAND_ALE_PIN);
bcm2835_smi_set_address(inst, addr);
if (cmd != NAND_CMD_NONE)
bcm2835_smi_write_buf(inst, &cmd32, 1);
}
static inline uint8_t bcm2835_smi_nand_read_byte(struct mtd_info *mtd)
{
uint8_t byte;
struct bcm2835_smi_nand_host *host = dev_get_drvdata(mtd->dev.parent);
struct bcm2835_smi_instance *inst = host->smi_inst;
bcm2835_smi_read_buf(inst, &byte, 1);
return byte;
}
static inline void bcm2835_smi_nand_write_byte(struct mtd_info *mtd,
uint8_t byte)
{
struct bcm2835_smi_nand_host *host = dev_get_drvdata(mtd->dev.parent);
struct bcm2835_smi_instance *inst = host->smi_inst;
bcm2835_smi_write_buf(inst, &byte, 1);
}
static inline void bcm2835_smi_nand_write_buf(struct mtd_info *mtd,
const uint8_t *buf, int len)
{
struct bcm2835_smi_nand_host *host = dev_get_drvdata(mtd->dev.parent);
struct bcm2835_smi_instance *inst = host->smi_inst;
bcm2835_smi_write_buf(inst, buf, len);
}
static inline void bcm2835_smi_nand_read_buf(struct mtd_info *mtd,
uint8_t *buf, int len)
{
struct bcm2835_smi_nand_host *host = dev_get_drvdata(mtd->dev.parent);
struct bcm2835_smi_instance *inst = host->smi_inst;
bcm2835_smi_read_buf(inst, buf, len);
}
/****************************************************************************
*
* Probe and remove functions
*
***************************************************************************/
static int bcm2835_smi_nand_probe(struct platform_device *pdev)
{
struct bcm2835_smi_nand_host *host;
struct nand_chip *this;
struct mtd_info *mtd;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node, *smi_node;
struct mtd_part_parser_data ppdata;
struct smi_settings *smi_settings;
struct bcm2835_smi_instance *smi_inst;
int ret = -ENXIO;
if (!node) {
dev_err(dev, "No device tree node supplied!");
return -EINVAL;
}
smi_node = of_parse_phandle(node, "smi_handle", 0);
/* Request use of SMI peripheral: */
smi_inst = bcm2835_smi_get(smi_node);
if (!smi_inst) {
dev_err(dev, "Could not register with SMI.");
return -EPROBE_DEFER;
}
/* Set SMI timing and bus width */
smi_settings = bcm2835_smi_get_settings_from_regs(smi_inst);
smi_settings->data_width = SMI_WIDTH_8BIT;
smi_settings->read_setup_time = 2;
smi_settings->read_hold_time = 1;
smi_settings->read_pace_time = 1;
smi_settings->read_strobe_time = 3;
smi_settings->write_setup_time = 2;
smi_settings->write_hold_time = 1;
smi_settings->write_pace_time = 1;
smi_settings->write_strobe_time = 3;
bcm2835_smi_set_regs_from_settings(smi_inst);
host = devm_kzalloc(dev, sizeof(struct bcm2835_smi_nand_host),
GFP_KERNEL);
if (!host)
return -ENOMEM;
host->dev = dev;
host->smi_inst = smi_inst;
platform_set_drvdata(pdev, host);
/* Link the structures together */
this = &host->nand_chip;
mtd = &host->mtd;
mtd->priv = this;
mtd->owner = THIS_MODULE;
mtd->dev.parent = dev;
mtd->name = DRIVER_NAME;
ppdata.of_node = node;
/* 20 us command delay time... */
this->chip_delay = 20;
this->priv = host;
this->cmd_ctrl = bcm2835_smi_nand_cmd_ctrl;
this->read_byte = bcm2835_smi_nand_read_byte;
this->write_byte = bcm2835_smi_nand_write_byte;
this->write_buf = bcm2835_smi_nand_write_buf;
this->read_buf = bcm2835_smi_nand_read_buf;
this->ecc.mode = NAND_ECC_SOFT;
/* Should never be accessed directly: */
this->IO_ADDR_R = (void *)0xdeadbeef;
this->IO_ADDR_W = (void *)0xdeadbeef;
/* First scan to find the device and get the page size */
if (nand_scan_ident(mtd, 1, NULL))
return -ENXIO;
/* Second phase scan */
if (nand_scan_tail(mtd))
return -ENXIO;
ret = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0);
if (!ret)
return 0;
nand_release(mtd);
return -EINVAL;
}
static int bcm2835_smi_nand_remove(struct platform_device *pdev)
{
struct bcm2835_smi_nand_host *host = platform_get_drvdata(pdev);
nand_release(&host->mtd);
return 0;
}
/****************************************************************************
*
* Register the driver with device tree
*
***************************************************************************/
static const struct of_device_id bcm2835_smi_nand_of_match[] = {
{.compatible = "brcm,bcm2835-smi-nand",},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, bcm2835_smi_nand_of_match);
static struct platform_driver bcm2835_smi_nand_driver = {
.probe = bcm2835_smi_nand_probe,
.remove = bcm2835_smi_nand_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = bcm2835_smi_nand_of_match,
},
};
module_platform_driver(bcm2835_smi_nand_driver);
MODULE_ALIAS("platform:smi-nand-bcm2835");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION
("Driver for NAND chips using Broadcom Secondary Memory Interface");
MODULE_AUTHOR("Luke Wren <luke@raspberrypi.org>");

Wyświetl plik

@ -1,26 +0,0 @@
#define ZF_LOG_LEVEL ZF_LOG_VERBOSE
#define ZF_LOG_DEF_SRCLOC ZF_LOG_SRCLOC_LONG
#define ZF_LOG_TAG "AT86RF215_Main"
#include "caribou_smi.h"
#include "bcm2835_smi.h"
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
int caribou_smi_init(caribou_smi_st* dev)
{
ZF_LOGI("initializing caribou_smi");
return 0;
}
int caribou_smi_close (caribou_smi_st* dev)
{
ZF_LOGI("closing caribou_smi");
return 0;
}

Wyświetl plik

@ -1,12 +0,0 @@
#ifndef __CARIBOU_SMI_H__
#define __CARIBOU_SMI_H__
typedef struct
{
int initialized;
} caribou_smi_st;
int caribou_smi_init(caribou_smi_st* dev);
int caribou_smi_close (caribou_smi_st* dev);
#endif // __CARIBOU_SMI_H__

Wyświetl plik

@ -1,125 +0,0 @@
# SMI Kernel Module
**Based on a doc. written By Michael Bishop - @CleverCa22 @ "https://github.com/librerpi/rpi-open-firmware"**
- "brcm,bcm2835-smi-dev" - creates a character device, smi_handle must point to the main smi instance
- "brcm,bcm2835-smi" - the main smi instance
In the default mode, there are separate read and write strobes, called SOE and SWE. when the **bus is idle, the rpi will drive the data pins low**.
During the setup SETUP period the strobe is high, and the address pins are presented. Then, if its a read operation, the data pins will switch to input mode at the start of SETUP. If its a write operation, the data will be presented on the data pins.
Then there is the STROBE phase, when either SOE or SWE goes low. For a read (SOE), the rpi will sample the data pins at the end of STROBE.
The HOLD phase just to let the lines settle after after strobe has been released. In the default mode, it does 16bit transfers.
If the full packet is an even multiple of 32bits (4 bytes), then the RPI can use dma to burst the whole thing, with just setup + strobe + hold clocks per 16bit (or 8-bits) transfer. If there is a stray 16bits at the end, the dma will do a dense burst with dma, but then do the stray 16bit seperately a while later
# SMI Clock
and from arch/arm/boot/dts/bcm270x.dtsi
```
Code: Select all
smi: smi@7e600000 {
...
assigned-clocks = <&clocks BCM2835_CLOCK_SMI>;
assigned-clock-rates = <125000000>;
```
Here we can see that the DTS sets the default clock of 125MHz to the SMI operation.
## Register SMI_CS (0x00) - Control + Status
| Bit | Name | Description
|-----|-------|-----------|
|0 | ENABLE||
|1 | DONE | returns 1 when done|
|2 | ACTIVE | returns 1 when doing a transfer
|3 | START | write 1 to start transfer
|4 | CLEAR | write 1 to clear fifo
|5 | WRITE | direction, 1=write, 0=read
|6:7 | PAD | for write, drop the first $PAD bytes in the fifo, for read, drop $PAD bytes from hw, before filling fifo
|8 | TEEN | tear effect mode enabled, transfers will wait for TE trigger
|9 | INTD | interrupt when done
|10 | INTT | interrupt on tx
|11 | INTR | interrupt on rx
|12 | PVMODE | enable pixelvalve mode
|13 | SETERR | write 1 to clear, turns 1 to signal a change to timing while ACTIVE
|14 | PXLDAT | enable pixel transfer modes
|15 | EDREQ | signals an external DREQ level
|24 | TBD| pixel ready?
|25 | TBD| axi fifo error, set to 1 to clear, sets itself if you read fifo when empty, or write fifo when full
|26 | TBD| tx fifo needs writing (under 1/4ths full)
|27 | TBD| rx fifo needs reading (either 3/4ths full, or DONE and not empty)
|28 | TBD| tx fifo has room
|29 | TBD| rx fifo contains data
|30 | TBD| tx fifo empty
|31 | TBD| rx fifo full
## Register SMI_L (0x04) - length / count
TBD
## Register SMI_A (0x08) - address
| bits | Name | Description|
|--|--|--|
| 0:5| TBD| address|
8:9|TBD| device address|
## Register SMI_D 0x0c data
SMI_DSR0 0x10 device0 read settings
SMI_DSW0 0x14 device0 write settings
SMI_DSR1 0x18 device1 read
SMI_DSW1 0x1c device1 write
SMI_DSR2 0x20
SMI_DSW2 0x24
SMI_DSR3 0x28
SMI_DSW3 0x2c
settings:
0:6 strobe 0-127 clock cycles to assert strobe
7 dreq use external dma request on sd16 to pace reads from device, sd17 to pace writes
8:14 pace clock cycles to wait between CS deassertion and start of next xfer
15 paceall if 1, use the PACE value, even if the next device is different
16:21 hold 0-63 clock cycles between strobe going inactive and cs/addr going inactive
22 read:fsetup 1: setup time only on first xfer after addr change, write: wswap(swap pixel data bits)
23 read:mode68 0:(oe+we) 1:(enable+dir), write: wformat(0=rgb565, 1=32bit rgba8888)
24:29 setup, clock cycles between chipselect/address, and read/write strobe
30:31 width, 00=8bit, 01==16bit, 10=18bit, 11=9bit
SMI_DC 0x30 dma control registers
24 dma passthru
28 dma enable
SMI_DCS 0x34 direct control/status register
0 ENABLE
1 START
2 DONE
3 WRITE
SMI_DA 0x38 direct address register
0:5 addr
8:9 device
SMI_DD 0x3c direct data registers
SMI_FD 0x40 FIFO debug register
0:5 FCNT current fifo count
8:13 FLVL high tide mark of FIFO count during most recent xfer
notes from experiments done with /dev/smi
the dma on the rpi can only send/recv 32bit chunks to the SMI (see drivers/char/broadcom/bcm2835_smi_dev.c odd_bytes)
any extra has to be sent as a second transaction? with an variable delay after the main burst
the main burst can operate at max speed with very dense packing
a burst involves a repeating setup, strobe, hold, setup, strobe, hold cycle
a burst is always followed by a pace period?
SOE goes low during the strobe period of a read?
the default smi clock on a pi0 is about 125mhz (cat /sys/kernel/debug/clk/smi/clk_rate)
bcm270x.dtsi defines the `assigned-clock-rates` to 125mhz
SA is held during at least strobe and hold, but vanishes during pace, bcm2835_smi.h claims its held during pace!!
during a read, SD0-SD17 are tristated at the start of SETUP, then driven low at the end of HOLD
during a read, SD0-SD17 are sampled at the end of STROBE
if 2 READ's occur back to back, the end of HOLD is the start of SETUP, and SD0-SD17 remain tri-state
in the default mode, SOE will strobe during reads, SWE strobes during writes

Wyświetl plik

@ -1,183 +0,0 @@
#include "bcm2835_smi.h"
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
static void print_smi_settings(struct smi_settings *settings)
{
printf("width: %d\n", settings->data_width);
printf("pack: %c\n", settings->pack_data ? 'Y' : 'N');
printf("read setup: %d, strobe: %d, hold: %d, pace: %d\n", settings->read_setup_time, settings->read_strobe_time, settings->read_hold_time, settings->read_pace_time);
printf("write setup: %d, strobe: %d, hold: %d, pace: %d\n", settings->write_setup_time, settings->write_strobe_time, settings->write_hold_time, settings->write_pace_time);
printf("dma enable: %c, passthru enable: %c\n", settings->dma_enable ? 'Y':'N', settings->dma_passthrough_enable ? 'Y':'N');
printf("dma threshold read: %d, write: %d\n", settings->dma_read_thresh, settings->dma_write_thresh);
printf("dma panic threshold read: %d, write: %d\n", settings->dma_panic_read_thresh, settings->dma_panic_write_thresh);
}
static void setup_settings (struct smi_settings *settings)
{
settings->read_setup_time = 1;
settings->read_strobe_time = 3;
settings->read_hold_time = 1;
settings->read_pace_time = 2;
settings->write_setup_time = 1;
settings->write_hold_time = 1;
settings->write_pace_time = 2;
settings->write_strobe_time = 3;
settings->data_width = SMI_WIDTH_8BIT;
settings->dma_enable = 1;
settings->pack_data = 1;
settings->dma_passthrough_enable = 1;
}
void DumpHex(const void* data, size_t size)
{
char ascii[17];
size_t i, j;
ascii[16] = '\0';
for (i = 0; i < size; ++i) {
printf("%02X ", ((unsigned char*)data)[i]);
if (((unsigned char*)data)[i] >= ' ' && ((unsigned char*)data)[i] <= '~') {
ascii[i % 16] = ((unsigned char*)data)[i];
} else {
ascii[i % 16] = '.';
}
if ((i+1) % 8 == 0 || i+1 == size) {
printf(" ");
if ((i+1) % 16 == 0) {
printf("| %s \n", ascii);
} else if (i+1 == size) {
ascii[(i+1) % 16] = '\0';
if ((i+1) % 16 <= 8) {
printf(" ");
}
for (j = (i+1) % 16; j < 16; ++j) {
printf(" ");
}
printf("| %s \n", ascii);
}
}
}
}
void timeout_read(int filedesc, char* buffer, int size_of_buf)
{
fd_set set;
struct timeval timeout;
int rv;
char *buff = buffer;
int len = size_of_buf;
//int filedesc = open( "dev/ttyS0", O_RDWR );
FD_ZERO(&set); /* clear the set */
FD_SET(filedesc, &set); /* add our file descriptor to the set */
timeout.tv_sec = 1;
timeout.tv_usec = 0;
rv = select(filedesc + 1, &set, NULL, NULL, &timeout);
if(rv == -1)
perror("select"); /* an error accured */
else if(rv == 0)
printf("timeout"); /* a timeout occured */
else
read( filedesc, buff, len ); /* there was data to read */
}
int main(int argc, char **argv)
{
int fd = open("/dev/smi", O_RDWR);
if (fd < 0)
{
perror("can't open smi driver file");
exit(1);
}
struct smi_settings settings;
int ret = ioctl(fd, BCM2835_SMI_IOC_GET_SETTINGS, &settings);
if (ret != 0)
{
perror("ioctl 1");
close (fd);
exit(1);
}
printf("Current settings:\n");
print_smi_settings(&settings);
setup_settings(&settings);
ret = ioctl(fd, BCM2835_SMI_IOC_WRITE_SETTINGS, &settings);
if (ret != 0)
{
perror("ioctl 1");
close (fd);
exit(1);
}
ret = ioctl(fd, BCM2835_SMI_IOC_ADDRESS, (5<<1));
printf("\n\nNEW settings:\n");
print_smi_settings(&settings);
bool writeMode = false;
// writeMode = true;
int count = 4096*32;
uint32_t buffer[count];
uint8_t* b8 = (uint8_t*)buffer;
if (writeMode)
{
for (int i=0; i<count; i++)
{
buffer[i] = i;
}
write(fd, buffer, count*sizeof(uint32_t));
}
else
{
int hist[256] = {0};
for (int j = 0; j < 1; j++)
{
timeout_read(fd, (uint8_t*)buffer, count*sizeof(uint32_t));
for (int i = 1; i<count*sizeof(uint32_t); i++)
{
hist[(uint8_t)(b8[i] - b8[i-1])] ++;
}
}
printf("Histogram , buffer[0] = %d, %d, %d, %d\n", ((uint8_t*)buffer)[0], ((uint8_t*)buffer)[1], ((uint8_t*)buffer)[2], ((uint8_t*)buffer)[3]);
int error_bytes = 0;
int total_bytes = 0;
for (int i =0; i<256; i++)
{
if (hist[i]>0)
{
if (i != 1) error_bytes += hist[i];
total_bytes += hist[i];
printf(" %d: %d\n", i, hist[i]);
}
}
printf(" Byte Error Rate: %.10g, %d total, %d errors\n", (float)(error_bytes) / (float)(total_bytes), total_bytes, error_bytes);
//DumpHex(buffer, count*sizeof(uint32_t));
puts("\n");
}
close (fd);
return 0;
}