kopia lustrzana https://github.com/cariboulabs/cariboulite
update READMEs
rodzic
f9f673601c
commit
12e8d3e7de
|
@ -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>");
|
|
|
@ -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 */
|
|
|
@ -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>");
|
|
|
@ -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>");
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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__
|
|
|
@ -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
|
|
|
@ -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;
|
|
||||||
|
|
||||||
}
|
|
Ładowanie…
Reference in New Issue