{"action":"created","comment":{"url":"https://api.github.com/repos/raspberrypi/linux/pulls/comments/38264787","id":38264787,"diff_hunk":"@@ -0,0 +1,1030 @@\n+/**\n+ * Broadcom Secondary Memory Interface driver\n+ *\n+ * Written by Luke Wren <luke@raspberrypi.org>\n+ * Copyright (c) 2015, Raspberry Pi (Trading) Ltd.\n+ *\n+ * Redistribution and use in source and binary forms, with or without\n+ * modification, are permitted provided that the following conditions\n+ * are met:\n+ * 1. Redistributions of source code must retain the above copyright\n+ *    notice, this list of conditions, and the following disclaimer,\n+ *    without modification.\n+ * 2. Redistributions in binary form must reproduce the above copyright\n+ *    notice, this list of conditions and the following disclaimer in the\n+ *    documentation and/or other materials provided with the distribution.\n+ * 3. The names of the above-listed copyright holders may not be used\n+ *    to endorse or promote products derived from this software without\n+ *    specific prior written permission.\n+ *\n+ * ALTERNATIVELY, this software may be distributed under the terms of the\n+ * GNU General Public License (\"GPL\") version 2, as published by the Free\n+ * Software Foundation.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n+ * IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+#include <linux/kernel.h>\n+#include <linux/module.h>\n+#include <linux/of.h>\n+#include <linux/platform_device.h>\n+#include <linux/of_address.h>\n+#include <linux/of_platform.h>\n+#include <linux/mm.h>\n+#include <linux/slab.h>\n+#include <linux/pagemap.h>\n+#include <linux/dma-mapping.h>\n+#include <linux/dmaengine.h>\n+#include <linux/semaphore.h>\n+#include <linux/spinlock.h>\n+#include <linux/io.h>\n+\n+#define BCM2835_SMI_IMPLEMENTATION\n+#include <linux/broadcom/bcm2835_smi.h>\n+\n+#define DRIVER_NAME \"smi-bcm2835\"\n+\n+#define N_PAGES_FROM_BYTES(n) ((n + PAGE_SIZE-1) / PAGE_SIZE)\n+\n+#define DMA_WRITE_TO_MEM true\n+#define DMA_READ_FROM_MEM false\n+\n+struct bcm2835_smi_instance {\n+\tstruct device *dev;\n+\tstruct smi_settings settings;\n+\t__iomem void *smi_regs_ptr, *cm_smi_regs_ptr;\n+\tdma_addr_t smi_regs_busaddr;\n+\n+\tstruct dma_chan *dma_chan;\n+\tstruct dma_slave_config dma_config;\n+\n+\tstruct bcm2835_smi_bounce_info bounce;\n+\n+\tstruct scatterlist *buffer_sgl;\n+\n+\tint clock_source;\n+\tint clock_divisor;\n+\n+\t/* Sometimes we are called into in an atomic context (e.g. by\n+\t   JFFS2 + MTD) so we can't use a mutex */\n+\tspinlock_t transaction_lock;\n+};\n+\n+/****************************************************************************\n+*\n+*   SMI clock manager setup\n+*\n+***************************************************************************/\n+\n+static inline void write_smi_cm_reg(struct bcm2835_smi_instance *inst,\n+\tu32 val, unsigned reg)\n+{\n+\twritel(CM_PWD | val, inst->cm_smi_regs_ptr + reg);\n+}\n+\n+static inline u32 read_smi_cm_reg(struct bcm2835_smi_instance *inst,\n+\tunsigned reg)\n+{\n+\treturn readl(inst->cm_smi_regs_ptr + reg);\n+}\n+\n+static void smi_setup_clock(struct bcm2835_smi_instance *inst)\n+{\n+\tdev_info(inst->dev, \"Setting up clock...\");\n+\t/* Disable SMI clock and wait for it to stop. */\n+\twrite_smi_cm_reg(inst, 0, CM_SMI_CTL);\n+\twhile (read_smi_cm_reg(inst, CM_SMI_CTL) & CM_SMI_CTL_BUSY)\n+\t\t;\n+\n+\twrite_smi_cm_reg(inst, (inst->clock_divisor << CM_SMI_DIV_DIVI_OFFS),\n+\t       CM_SMI_DIV);\n+\twrite_smi_cm_reg(inst, (inst->clock_source << CM_SMI_CTL_SRC_OFFS),\n+\t       CM_SMI_CTL);\n+\n+\t/* Enable the clock */\n+\twrite_smi_cm_reg(inst, (inst->clock_source << CM_SMI_CTL_SRC_OFFS) |\n+\t       CM_SMI_CTL_ENAB, CM_SMI_CTL);\n+}\n+\n+/****************************************************************************\n+*\n+*   SMI peripheral setup\n+*\n+***************************************************************************/\n+\n+static inline void write_smi_reg(struct bcm2835_smi_instance *inst,\n+\tu32 val, unsigned reg)\n+{\n+\twritel(val, inst->smi_regs_ptr + reg);\n+}\n+\n+static inline u32 read_smi_reg(struct bcm2835_smi_instance *inst, unsigned reg)\n+{\n+\treturn readl(inst->smi_regs_ptr + reg);\n+}\n+\n+/* Token-paste macro for e.g SMIDSR_RSTROBE ->  value of SMIDSR_RSTROBE_MASK */\n+#define _CONCAT(x, y) x##y\n+#define CONCAT(x, y) _CONCAT(x, y)\n+\n+#define SET_BIT_FIELD(dest, field, bits) ((dest) = \\\n+\t((dest) & ~CONCAT(field, _MASK)) | (((bits) << CONCAT(field, _OFFS))& \\\n+\t CONCAT(field, _MASK)))\n+#define GET_BIT_FIELD(src, field) (((src) & \\\n+\tCONCAT(field, _MASK)) >> CONCAT(field, _OFFS))\n+\n+static void smi_dump_context_labelled(struct bcm2835_smi_instance *inst,\n+\tconst char *label)\n+{\n+\tdev_err(inst->dev, \"SMI context dump: %s\", label);\n+\tdev_err(inst->dev, \"SMICS:  0x%08x\", read_smi_reg(inst, SMICS));\n+\tdev_err(inst->dev, \"SMIL:   0x%08x\", read_smi_reg(inst, SMIL));\n+\tdev_err(inst->dev, \"SMIDSR: 0x%08x\", read_smi_reg(inst, SMIDSR0));\n+\tdev_err(inst->dev, \"SMIDSW: 0x%08x\", read_smi_reg(inst, SMIDSW0));\n+\tdev_err(inst->dev, \"SMIDC:  0x%08x\", read_smi_reg(inst, SMIDC));\n+\tdev_err(inst->dev, \"SMIFD:  0x%08x\", read_smi_reg(inst, SMIFD));\n+\tdev_err(inst->dev, \" \");\n+}\n+\n+static inline void smi_dump_context(struct bcm2835_smi_instance *inst)\n+{\n+\tsmi_dump_context_labelled(inst, \"\");\n+}\n+\n+static void smi_get_default_settings(struct bcm2835_smi_instance *inst)\n+{\n+\tstruct smi_settings *settings = &inst->settings;\n+\n+\tsettings->data_width = SMI_WIDTH_16BIT;\n+\tsettings->pack_data = true;\n+\n+\tsettings->read_setup_time = 1;\n+\tsettings->read_hold_time = 1;\n+\tsettings->read_pace_time = 1;\n+\tsettings->read_strobe_time = 3;\n+\n+\tsettings->write_setup_time = settings->read_setup_time;\n+\tsettings->write_hold_time = settings->read_hold_time;\n+\tsettings->write_pace_time = settings->read_pace_time;\n+\tsettings->write_strobe_time = settings->read_strobe_time;\n+\n+\tsettings->dma_enable = true;\n+\tsettings->dma_passthrough_enable = false;\n+\tsettings->dma_read_thresh = 0x01;\n+\tsettings->dma_write_thresh = 0x3f;\n+\tsettings->dma_panic_read_thresh = 0x20;\n+\tsettings->dma_panic_write_thresh = 0x20;\n+}\n+\n+void bcm2835_smi_set_regs_from_settings(struct bcm2835_smi_instance *inst)\n+{\n+\tstruct smi_settings *settings = &inst->settings;\n+\tint smidsr_temp = 0, smidsw_temp = 0, smics_temp,\n+\t    smidcs_temp, smidc_temp = 0;\n+\n+\tspin_lock(&inst->transaction_lock);\n+\n+\t/* temporarily disable the peripheral: */\n+\tsmics_temp = read_smi_reg(inst, SMICS);\n+\twrite_smi_reg(inst, 0, SMICS);\n+\tsmidcs_temp = read_smi_reg(inst, SMIDCS);\n+\twrite_smi_reg(inst, 0, SMIDCS);\n+\n+\tif (settings->pack_data)\n+\t\tsmics_temp |= SMICS_PXLDAT;\n+\telse\n+\t\tsmics_temp &= ~SMICS_PXLDAT;\n+\n+\tSET_BIT_FIELD(smidsr_temp, SMIDSR_RWIDTH, settings->data_width);\n+\tSET_BIT_FIELD(smidsr_temp, SMIDSR_RSETUP, settings->read_setup_time);\n+\tSET_BIT_FIELD(smidsr_temp, SMIDSR_RHOLD, settings->read_hold_time);\n+\tSET_BIT_FIELD(smidsr_temp, SMIDSR_RPACE, settings->read_pace_time);\n+\tSET_BIT_FIELD(smidsr_temp, SMIDSR_RSTROBE, settings->read_strobe_time);\n+\twrite_smi_reg(inst, smidsr_temp, SMIDSR0);\n+\n+\tSET_BIT_FIELD(smidsw_temp, SMIDSW_WWIDTH, settings->data_width);\n+\tif (settings->data_width == SMI_WIDTH_8BIT)\n+\t\tsmidsw_temp |= SMIDSW_WSWAP;\n+\telse\n+\t\tsmidsw_temp &= ~SMIDSW_WSWAP;\n+\tSET_BIT_FIELD(smidsw_temp, SMIDSW_WSETUP, settings->write_setup_time);\n+\tSET_BIT_FIELD(smidsw_temp, SMIDSW_WHOLD, settings->write_hold_time);\n+\tSET_BIT_FIELD(smidsw_temp, SMIDSW_WPACE, settings->write_pace_time);\n+\tSET_BIT_FIELD(smidsw_temp, SMIDSW_WSTROBE,\n+\t\t\tsettings->write_strobe_time);\n+\twrite_smi_reg(inst, smidsw_temp, SMIDSW0);\n+\n+\tSET_BIT_FIELD(smidc_temp, SMIDC_REQR, settings->dma_read_thresh);\n+\tSET_BIT_FIELD(smidc_temp, SMIDC_REQW, settings->dma_write_thresh);\n+\tSET_BIT_FIELD(smidc_temp, SMIDC_PANICR,\n+\t\t      settings->dma_panic_read_thresh);\n+\tSET_BIT_FIELD(smidc_temp, SMIDC_PANICW,\n+\t\t      settings->dma_panic_write_thresh);\n+\tif (settings->dma_passthrough_enable) {\n+\t\tsmidc_temp |= SMIDC_DMAP;\n+\t\tsmidsr_temp |= SMIDSR_RDREQ;\n+\t\twrite_smi_reg(inst, smidsr_temp, SMIDSR0);\n+\t\tsmidsw_temp |= SMIDSW_WDREQ;\n+\t\twrite_smi_reg(inst, smidsw_temp, SMIDSW0);\n+\t} else\n+\t\tsmidc_temp &= ~SMIDC_DMAP;\n+\tif (settings->dma_enable)\n+\t\tsmidc_temp |= SMIDC_DMAEN;\n+\telse\n+\t\tsmidc_temp &= ~SMIDC_DMAEN;\n+\n+\twrite_smi_reg(inst, smidc_temp, SMIDC);\n+\n+\t/* re-enable (if was previously enabled) */\n+\twrite_smi_reg(inst, smics_temp, SMICS);\n+\twrite_smi_reg(inst, smidcs_temp, SMIDCS);\n+\n+\tspin_unlock(&inst->transaction_lock);\n+}\n+EXPORT_SYMBOL(bcm2835_smi_set_regs_from_settings);\n+\n+struct smi_settings *bcm2835_smi_get_settings_from_regs\n+\t(struct bcm2835_smi_instance *inst)\n+{\n+\tstruct smi_settings *settings = &inst->settings;\n+\tint smidsr, smidsw, smidc;\n+\n+\tspin_lock(&inst->transaction_lock);\n+\n+\tsmidsr = read_smi_reg(inst, SMIDSR0);\n+\tsmidsw = read_smi_reg(inst, SMIDSW0);\n+\tsmidc = read_smi_reg(inst, SMIDC);\n+\n+\tsettings->pack_data = (read_smi_reg(inst, SMICS) & SMICS_PXLDAT) ?\n+\t    true : false;\n+\n+\tsettings->data_width = GET_BIT_FIELD(smidsr, SMIDSR_RWIDTH);\n+\tsettings->read_setup_time = GET_BIT_FIELD(smidsr, SMIDSR_RSETUP);\n+\tsettings->read_hold_time = GET_BIT_FIELD(smidsr, SMIDSR_RHOLD);\n+\tsettings->read_pace_time = GET_BIT_FIELD(smidsr, SMIDSR_RPACE);\n+\tsettings->read_strobe_time = GET_BIT_FIELD(smidsr, SMIDSR_RSTROBE);\n+\n+\tsettings->write_setup_time = GET_BIT_FIELD(smidsw, SMIDSW_WSETUP);\n+\tsettings->write_hold_time = GET_BIT_FIELD(smidsw, SMIDSW_WHOLD);\n+\tsettings->write_pace_time = GET_BIT_FIELD(smidsw, SMIDSW_WPACE);\n+\tsettings->write_strobe_time = GET_BIT_FIELD(smidsw, SMIDSW_WSTROBE);\n+\n+\tsettings->dma_read_thresh = GET_BIT_FIELD(smidc, SMIDC_REQR);\n+\tsettings->dma_write_thresh = GET_BIT_FIELD(smidc, SMIDC_REQW);\n+\tsettings->dma_panic_read_thresh = GET_BIT_FIELD(smidc, SMIDC_PANICR);\n+\tsettings->dma_panic_write_thresh = GET_BIT_FIELD(smidc, SMIDC_PANICW);\n+\tsettings->dma_passthrough_enable = (smidc & SMIDC_DMAP) ? true : false;\n+\tsettings->dma_enable = (smidc & SMIDC_DMAEN) ? true : false;\n+\n+\tspin_unlock(&inst->transaction_lock);\n+\n+\treturn settings;\n+}\n+EXPORT_SYMBOL(bcm2835_smi_get_settings_from_regs);\n+\n+static inline void smi_set_address(struct bcm2835_smi_instance *inst,\n+\tunsigned int address)\n+{\n+\tint smia_temp = 0, smida_temp = 0;\n+\n+\tSET_BIT_FIELD(smia_temp, SMIA_ADDR, address);\n+\tSET_BIT_FIELD(smida_temp, SMIDA_ADDR, address);\n+\n+\t/* Write to both address registers - user doesn't care whether we're\n+\t   doing programmed or direct transfers. */\n+\twrite_smi_reg(inst, smia_temp, SMIA);\n+\twrite_smi_reg(inst, smida_temp, SMIDA);\n+}\n+\n+static void smi_setup_regs(struct bcm2835_smi_instance *inst)\n+{\n+\n+\tdev_info(inst->dev, \"Initialising SMI registers...\");\n+\t/* Disable the peripheral if already enabled */\n+\twrite_smi_reg(inst, 0, SMICS);\n+\twrite_smi_reg(inst, 0, SMIDCS);\n+\n+\tsmi_get_default_settings(inst);\n+\tbcm2835_smi_set_regs_from_settings(inst);\n+\tsmi_set_address(inst, 0);\n+\n+\twrite_smi_reg(inst, read_smi_reg(inst, SMICS) | SMICS_ENABLE, SMICS);\n+\twrite_smi_reg(inst, read_smi_reg(inst, SMIDCS) | SMIDCS_ENABLE,\n+\t\tSMIDCS);\n+}\n+\n+/****************************************************************************\n+*\n+*   Low-level SMI access functions\n+*   Other modules should use the exported higher-level functions e.g.\n+*   bcm2835_smi_write_buf() unless they have a good reason to use these\n+*\n+***************************************************************************/\n+\n+static inline uint32_t smi_read_single_word(struct bcm2835_smi_instance *inst)\n+{\n+\tint timeout = 0;\n+\n+\twrite_smi_reg(inst, SMIDCS_ENABLE, SMIDCS);\n+\twrite_smi_reg(inst, SMIDCS_ENABLE | SMIDCS_START, SMIDCS);\n+\t/* Make sure things happen in the right order...*/\n+\tmb();\n+\twhile (!(read_smi_reg(inst, SMIDCS) & SMIDCS_DONE) &&\n+\t\t++timeout < 10000)\n+\t\t;\n+\tif (timeout < 10000)\n+\t\treturn read_smi_reg(inst, SMIDD);\n+\n+\tdev_err(inst->dev,\n+\t\t\"SMI direct read timed out (is the clock set up correctly?)\");\n+\treturn 0;\n+}\n+\n+static inline void smi_write_single_word(struct bcm2835_smi_instance *inst,\n+\tuint32_t data)\n+{\n+\tint timeout = 0;\n+\n+\twrite_smi_reg(inst, SMIDCS_ENABLE | SMIDCS_WRITE, SMIDCS);\n+\twrite_smi_reg(inst, data, SMIDD);\n+\twrite_smi_reg(inst, SMIDCS_ENABLE | SMIDCS_WRITE | SMIDCS_START,\n+\t\tSMIDCS);\n+\n+\twhile (!(read_smi_reg(inst, SMIDCS) & SMIDCS_DONE) &&\n+\t\t++timeout < 10000)\n+\t\t;\n+\tif (timeout >= 10000)\n+\t\tdev_err(inst->dev,\n+\t\t\"SMI direct write timed out (is the clock set up correctly?)\");\n+}\n+\n+/* Initiates a programmed read into the read FIFO. It is up to the caller to\n+ * read data from the FIFO -  either via paced DMA transfer,\n+ * or polling SMICS_RXD to check whether data is available.\n+ * SMICS_ACTIVE will go low upon completion. */\n+static void smi_init_programmed_read(struct bcm2835_smi_instance *inst,\n+\tint num_transfers)\n+{\n+\tint smics_temp;\n+\n+\t/* Disable the peripheral: */\n+\tsmics_temp = read_smi_reg(inst, SMICS) & ~(SMICS_ENABLE | SMICS_WRITE);\n+\twrite_smi_reg(inst, smics_temp, SMICS);\n+\twhile (read_smi_reg(inst, SMICS) & SMICS_ENABLE)\n+\t\t;\n+\n+\t/* Program the transfer count: */\n+\twrite_smi_reg(inst, num_transfers, SMIL);\n+\n+\t/* re-enable and start: */\n+\tsmics_temp |= SMICS_ENABLE;\n+\twrite_smi_reg(inst, smics_temp, SMICS);\n+\tsmics_temp |= SMICS_CLEAR;\n+\t/* Just to be certain: */\n+\tmb();\n+\twhile (read_smi_reg(inst, SMICS) & SMICS_ACTIVE)\n+\t\t;\n+\twrite_smi_reg(inst, smics_temp, SMICS);\n+\tsmics_temp |= SMICS_START;\n+\twrite_smi_reg(inst, smics_temp, SMICS);\n+\t/*dev_info(inst->dev, \"rx started.\");*/\n+}\n+\n+/* Initiates a programmed write sequence, using data from the write FIFO.\n+ * It is up to the caller to initiate a DMA transfer before calling,\n+ * or use another method to keep the write FIFO topped up.\n+ * SMICS_ACTIVE will go low upon completion.\n+ */\n+static void smi_init_programmed_write(struct bcm2835_smi_instance *inst,\n+\tint num_transfers)\n+{\n+\tint smics_temp;\n+\n+\t/* Disable the peripheral: */\n+\tsmics_temp = read_smi_reg(inst, SMICS) & ~SMICS_ENABLE;\n+\twrite_smi_reg(inst, smics_temp, SMICS);\n+\twhile (read_smi_reg(inst, SMICS) & SMICS_ENABLE)\n+\t\t;\n+\n+\t/* Program the transfer count: */\n+\twrite_smi_reg(inst, num_transfers, SMIL);\n+\n+\t/* setup, re-enable and start: */\n+\tsmics_temp |= SMICS_WRITE | SMICS_ENABLE;\n+\twrite_smi_reg(inst, smics_temp, SMICS);\n+\tsmics_temp |= SMICS_START;\n+\twrite_smi_reg(inst, smics_temp, SMICS);\n+}\n+\n+/* Initiate a read and then poll FIFO for data, reading out as it appears. */\n+static void smi_read_fifo(struct bcm2835_smi_instance *inst,\n+\tuint32_t *dest, int n_bytes)\n+{\n+\tif (read_smi_reg(inst, SMICS) & SMICS_RXD) {\n+\t\tsmi_dump_context_labelled(inst,\n+\t\t\t\"WARNING: read FIFO not empty at start of read call.\");\n+\t\twhile (read_smi_reg(inst, SMICS))\n+\t\t\t;\n+\t}\n+\n+\t/* Dispatch the read: */\n+\tif (inst->settings.data_width == SMI_WIDTH_8BIT)\n+\t\tsmi_init_programmed_read(inst, n_bytes);\n+\telse if (inst->settings.data_width == SMI_WIDTH_16BIT)\n+\t\tsmi_init_programmed_read(inst, n_bytes / 2);\n+\telse {\n+\t\tdev_err(inst->dev, \"Unsupported data width for read.\");\n+\t\treturn;\n+\t}\n+\n+\t/* Poll FIFO to keep it empty */\n+\twhile (!(read_smi_reg(inst, SMICS) & SMICS_DONE))\n+\t\tif (read_smi_reg(inst, SMICS) & SMICS_RXD)\n+\t\t\t*dest++ = read_smi_reg(inst, SMID);\n+\n+\t/* Ensure that the FIFO is emptied */\n+\tif (read_smi_reg(inst, SMICS) & SMICS_RXD) {\n+\t\tint fifo_count;\n+\n+\t\tfifo_count = GET_BIT_FIELD(read_smi_reg(inst, SMIFD),\n+\t\t\tSMIFD_FCNT);\n+\t\twhile (fifo_count--)\n+\t\t\t*dest++ = read_smi_reg(inst, SMID);\n+\t}\n+\n+\tif (!(read_smi_reg(inst, SMICS) & SMICS_DONE))\n+\t\tsmi_dump_context_labelled(inst,\n+\t\t\t\"WARNING: transaction finished but done bit not set.\");\n+\n+\tif (read_smi_reg(inst, SMICS) & SMICS_RXD)\n+\t\tsmi_dump_context_labelled(inst,\n+\t\t\t\"WARNING: read FIFO not empty at end of read call.\");\n+\n+}\n+\n+/* Initiate a write, and then keep the FIFO topped up. */\n+static void smi_write_fifo(struct bcm2835_smi_instance *inst,\n+\tuint32_t *src, int n_bytes)\n+{\n+\tint i, timeout = 0;\n+\n+\t/* Empty FIFOs if not already so */\n+\tif (!(read_smi_reg(inst, SMICS) & SMICS_TXE)) {\n+\t\tsmi_dump_context_labelled(inst,\n+\t\t    \"WARNING: write fifo not empty at start of write call.\");\n+\t\twrite_smi_reg(inst, read_smi_reg(inst, SMICS) | SMICS_CLEAR,\n+\t\t\tSMICS);\n+\t}\n+\n+\t/* Initiate the transfer */\n+\tif (inst->settings.data_width == SMI_WIDTH_8BIT)\n+\t\tsmi_init_programmed_write(inst, n_bytes);\n+\telse if (inst->settings.data_width == SMI_WIDTH_16BIT)\n+\t\tsmi_init_programmed_write(inst, n_bytes / 2);\n+\telse {\n+\t\tdev_err(inst->dev, \"Unsupported data width for write.\");\n+\t\treturn;\n+\t}\n+\t/* Fill the FIFO: */\n+\tfor (i = 0; i < (n_bytes - 1) / 4 + 1; ++i) {\n+\t\twhile (!(read_smi_reg(inst, SMICS) & SMICS_TXD))\n+\t\t\t;\n+\t\twrite_smi_reg(inst, *src++, SMID);\n+\t}\n+\t/* Busy wait... */\n+\twhile (!(read_smi_reg(inst, SMICS) & SMICS_DONE) && ++timeout <\n+\t\t1000000)\n+\t\t;\n+\tif (timeout >= 1000000)\n+\t\tsmi_dump_context_labelled(inst,\n+\t\t\t\"Timed out on write operation!\");\n+\tif (!(read_smi_reg(inst, SMICS) & SMICS_TXE))\n+\t\tsmi_dump_context_labelled(inst,\n+\t\t\t\"WARNING: FIFO not empty at end of write operation.\");\n+}\n+\n+/****************************************************************************\n+*\n+*   SMI DMA operations\n+*\n+***************************************************************************/\n+\n+/* Disable SMI and put it into the correct direction before doing DMA setup.\n+   Stops spurious DREQs during setup. Peripheral is re-enabled by init_*() */\n+static void smi_disable(struct bcm2835_smi_instance *inst,\n+\tenum dma_transfer_direction direction)\n+{\n+\tint smics_temp = read_smi_reg(inst, SMICS) & ~SMICS_ENABLE;\n+\n+\tif (direction == DMA_DEV_TO_MEM)\n+\t\tsmics_temp &= ~SMICS_WRITE;\n+\telse\n+\t\tsmics_temp |= SMICS_WRITE;\n+\twrite_smi_reg(inst, smics_temp, SMICS);\n+\twhile (read_smi_reg(inst, SMICS) & SMICS_ACTIVE)\n+\t\t;\n+}\n+\n+static struct scatterlist *_smi_scatterlist_from_buffer(\n+\tstruct bcm2835_smi_instance *inst,\n+\tdma_addr_t buf,\n+\tsize_t len,\n+\tbool alloc_new_list)\n+{\n+\tstruct scatterlist *sg;\n+\n+\t/* We do NOT want kmalloc to sleep! May be called in atomic context. */\n+\tif (alloc_new_list)\n+\t\tsg = devm_kzalloc(inst->dev, sizeof(*sg), GFP_NOWAIT);\n+\telse\n+\t\tsg = inst->buffer_sgl;\n+\n+\tif (!sg) {\n+\t\tdev_err(inst->dev,\n+\t\t\t\"scatterlist_from_buffer: kzalloc returned NULL!\");\n+\t\treturn NULL;\n+\t}\n+\tsg_init_table(sg, 1);\n+\tsg_dma_address(sg) = buf;\n+\tsg_dma_len(sg) = len;\n+\treturn sg;\n+}\n+\n+static inline struct scatterlist *smi_scatterlist_from_buffer(\n+\tstruct bcm2835_smi_instance *inst,\n+\tdma_addr_t buf,\n+\tsize_t len)\n+{\n+\treturn _smi_scatterlist_from_buffer(inst, buf, len, false);\n+}\n+\n+/* The caller is responsible for freeing this scatterlist */\n+\n+static inline struct scatterlist *smi_alloc_scatterlist_from_buffer(\n+\tstruct bcm2835_smi_instance *inst,\n+\tdma_addr_t buf,\n+\tsize_t len)\n+{\n+\treturn _smi_scatterlist_from_buffer(inst, buf, len, true);\n+}\n+\n+static void smi_dma_callback_user_copy(void *param)\n+{\n+\t/* Notify the bottom half that a chunk is ready for user copy */\n+\tstruct bcm2835_smi_instance *inst =\n+\t\t(struct bcm2835_smi_instance *)param;\n+\n+\tup(&inst->bounce.callback_sem);\n+}\n+\n+/* Creates a descriptor, assigns the given callback, and submits the\n+   descriptor to dmaengine. Does not block - can queue up multiple\n+   descriptors and then wait for them all to complete.\n+   sg_len is the number of control blocks, NOT the number of bytes.\n+   dir can be DMA_MEM_TO_DEV or DMA_DEV_TO_MEM.\n+   callback can be NULL - in this case it is not called. */\n+static inline struct dma_async_tx_descriptor *smi_dma_submit_sgl(\n+\tstruct bcm2835_smi_instance *inst,\n+\tstruct scatterlist *sgl,\n+\tsize_t sg_len,\n+\tenum dma_transfer_direction dir,\n+\tdma_async_tx_callback callback)\n+{\n+\tstruct dma_async_tx_descriptor *desc;\n+\n+\tdesc = dmaengine_prep_slave_sg(inst->dma_chan,\n+\t\t\t\t       sgl,\n+\t\t\t\t       sg_len,\n+\t\t\t\t       dir,\n+\t\t\t\t       DMA_PREP_INTERRUPT | DMA_CTRL_ACK |\n+\t\t\t\t       DMA_PREP_FENCE);\n+\tif (!desc) {\n+\t\tdev_err(inst->dev, \"read_sgl: dma slave preparation failed!\");\n+\t\twrite_smi_reg(inst, read_smi_reg(inst, SMICS) & ~SMICS_ACTIVE,\n+\t\t\tSMICS);\n+\t\twhile (read_smi_reg(inst, SMICS) & SMICS_ACTIVE)\n+\t\t\tcpu_relax();\n+\t\twrite_smi_reg(inst, read_smi_reg(inst, SMICS) | SMICS_ACTIVE,\n+\t\t\tSMICS);\n+\t\treturn NULL;\n+\t}\n+\tdesc->callback = callback;\n+\tdesc->callback_param = inst;\n+\tif (dmaengine_submit(desc) < 0)\n+\t\treturn NULL;\n+\treturn desc;\n+}\n+\n+/* NB this function blocks until the transfer is complete */\n+static void\n+smi_dma_read_sgl(struct bcm2835_smi_instance *inst,\n+\tstruct scatterlist *sgl, size_t sg_len, size_t n_bytes)\n+{\n+\tstruct dma_async_tx_descriptor *desc;\n+\n+\t/* Disable SMI and set to read before dispatching DMA - if SMI is in\n+\t * write mode and TX fifo is empty, it will generate a DREQ which may\n+\t * cause the read DMA to complete before the SMI read command is even\n+\t * dispatched! We want to dispatch DMA before SMI read so that reading\n+\t * is gapless, for logic analyser.\n+\t */\n+\n+\tsmi_disable(inst, DMA_DEV_TO_MEM);\n+\n+\tdesc = smi_dma_submit_sgl(inst, sgl, sg_len, DMA_DEV_TO_MEM, NULL);\n+\tdma_async_issue_pending(inst->dma_chan);\n+\n+\tif (inst->settings.data_width == SMI_WIDTH_8BIT)\n+\t\tsmi_init_programmed_read(inst, n_bytes);\n+\telse\n+\t\tsmi_init_programmed_read(inst, n_bytes / 2);\n+\n+\tif (dma_wait_for_async_tx(desc) == DMA_ERROR)\n+\t\tsmi_dump_context_labelled(inst, \"DMA timeout!\");\n+}\n+\n+static void\n+smi_dma_write_sgl(struct bcm2835_smi_instance *inst,\n+\tstruct scatterlist *sgl, size_t sg_len, size_t n_bytes)\n+{\n+\tstruct dma_async_tx_descriptor *desc;\n+\n+\tif (inst->settings.data_width == SMI_WIDTH_8BIT)\n+\t\tsmi_init_programmed_write(inst, n_bytes);\n+\telse\n+\t\tsmi_init_programmed_write(inst, n_bytes / 2);\n+\n+\tdesc = smi_dma_submit_sgl(inst, sgl, sg_len, DMA_MEM_TO_DEV, NULL);\n+\tdma_async_issue_pending(inst->dma_chan);\n+\n+\tif (dma_wait_for_async_tx(desc) == DMA_ERROR)\n+\t\tsmi_dump_context_labelled(inst, \"DMA timeout!\");\n+\telse\n+\t\t/* Wait for SMI to finish our writes */\n+\t\twhile (!(read_smi_reg(inst, SMICS) & SMICS_DONE))\n+\t\t\tcpu_relax();\n+}\n+\n+ssize_t bcm2835_smi_user_dma(\n+\tstruct bcm2835_smi_instance *inst,\n+\tenum dma_transfer_direction dma_dir,\n+\tchar __user *user_ptr, size_t count,\n+\tstruct bcm2835_smi_bounce_info **bounce)\n+{\n+\tint chunk_no = 0, chunk_size, count_left = count;\n+\tvoid (*init_trans_func)(struct bcm2835_smi_instance *, int);\n+\n+\tspin_lock(&inst->transaction_lock);\n+\n+\tif (dma_dir == DMA_DEV_TO_MEM)\n+\t\tinit_trans_func = smi_init_programmed_read;\n+\telse\n+\t\tinit_trans_func = smi_init_programmed_write;\n+\n+\tsmi_disable(inst, dma_dir);\n+\n+\tsema_init(&inst->bounce.callback_sem, 0);\n+\tif (bounce)\n+\t\t*bounce = &inst->bounce;\n+\twhile (count_left) {\n+\t\tchunk_size = count_left > DMA_BOUNCE_BUFFER_SIZE ?\n+\t\t\tDMA_BOUNCE_BUFFER_SIZE : count_left;\n+\t\tif (!smi_dma_submit_sgl(inst,\n+\t\t\tchunk_size == DMA_BOUNCE_BUFFER_SIZE ?\n+\t\t\tinst->bounce.sgl[chunk_no % DMA_BOUNCE_BUFFER_COUNT] :\n+\t\t\tsmi_scatterlist_from_buffer(inst, inst->bounce.phys[\n+\t\t\t\tchunk_no % DMA_BOUNCE_BUFFER_COUNT],\n+\t\t\t\tchunk_size),\n+\t\t\t1, dma_dir, smi_dma_callback_user_copy\n+\t\t)) {\n+\t\t\tdev_err(inst->dev, \"sgl submit failed\");\n+\t\t\tcount = 0;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tcount_left -= chunk_size;\n+\t\tchunk_no++;\n+\t}\n+\tdma_async_issue_pending(inst->dma_chan);\n+\n+\tif (inst->settings.data_width == SMI_WIDTH_8BIT)\n+\t\tinit_trans_func(inst, count);\n+\telse if (inst->settings.data_width == SMI_WIDTH_16BIT)\n+\t\tinit_trans_func(inst, count / 2);\n+\n+\n+\tdev_info(inst->dev, \"initiated transfer.\");\n+out:\n+\tspin_unlock(&inst->transaction_lock);\n+\treturn count;\n+}\n+EXPORT_SYMBOL(bcm2835_smi_user_dma);\n+\n+\n+/****************************************************************************\n+*\n+*   High level buffer transfer functions - for use by other drivers\n+*\n+***************************************************************************/\n+\n+/* Buffer must be physically contiguous - i.e. kmalloc, not vmalloc! */\n+void bcm2835_smi_write_buf(\n+\tstruct bcm2835_smi_instance *inst,\n+\tconst void *buf, size_t n_bytes)\n+{\n+\tint odd_bytes = n_bytes & 0x3;\n+\n+\tn_bytes -= odd_bytes;\n+\n+\tspin_lock(&inst->transaction_lock);\n+\n+\tif (n_bytes > DMA_THRESHOLD_BYTES) {\n+\t\tdma_addr_t phy_addr = dma_map_single(\n+\t\t\tinst->dev,\n+\t\t\t(void *)buf,\n+\t\t\tn_bytes,\n+\t\t\tDMA_MEM_TO_DEV);\n+\t\tstruct scatterlist *sgl =\n+\t\t\tsmi_scatterlist_from_buffer(inst, phy_addr, n_bytes);\n+\n+\t\tif (!sgl) {\n+\t\t\tsmi_dump_context_labelled(inst,\n+\t\t\t\"Error: could not create scatterlist for write!\");\n+\t\t\tgoto out;\n+\t\t}\n+\t\tsmi_dma_write_sgl(inst, sgl, 1, n_bytes);\n+\n+\t\tdma_unmap_single\n+\t\t\t(inst->dev, phy_addr, n_bytes, DMA_MEM_TO_DEV);\n+\t} else if (n_bytes) {\n+\t\tsmi_write_fifo(inst, (uint32_t *) buf, n_bytes);\n+\t}\n+\tbuf += n_bytes;\n+\n+\tif (inst->settings.data_width == SMI_WIDTH_8BIT) {\n+\t\twhile (odd_bytes--)\n+\t\t\tsmi_write_single_word(inst, *(uint8_t *) (buf++));\n+\t} else {\n+\t\twhile (odd_bytes >= 2) {\n+\t\t\tsmi_write_single_word(inst, *(uint16_t *)buf);\n+\t\t\tbuf += 2;\n+\t\t\todd_bytes -= 2;\n+\t\t}\n+\t\tif (odd_bytes) {\n+\t\t\t/* Reading an odd number of bytes on a 16 bit bus is\n+\t\t\t   a user bug. It's kinder to fail early and tell them\n+\t\t\t   than to e.g. transparently give them the bottom byte\n+\t\t\t   of a 16 bit transfer. */\n+\t\t\tdev_err(inst->dev,\n+\t\t\"WARNING: odd number of bytes specified for wide transfer.\");\n+\t\t\tdev_err(inst->dev,\n+\t\t\"At least one byte dropped as a result.\");\n+\t\t\tdump_stack();\n+\t\t}\n+\t}\n+out:\n+\tspin_unlock(&inst->transaction_lock);\n+}\n+EXPORT_SYMBOL(bcm2835_smi_write_buf);\n+\n+void bcm2835_smi_read_buf(struct bcm2835_smi_instance *inst,\n+\tvoid *buf, size_t n_bytes)\n+{\n+\n+\t/* SMI is inherently 32-bit, which causes surprising amounts of mess\n+\t   for bytes % 4 != 0. Easiest to avoid this mess altogether\n+\t   by handling remainder separately. */\n+\tint odd_bytes = n_bytes & 0x3;\n+\n+\tspin_lock(&inst->transaction_lock);\n+\tn_bytes -= odd_bytes;\n+\tif (n_bytes > DMA_THRESHOLD_BYTES) {\n+\t\tdma_addr_t phy_addr = dma_map_single(inst->dev,\n+\t\t\t\t\t\t     buf, n_bytes,\n+\t\t\t\t\t\t     DMA_DEV_TO_MEM);\n+\t\tstruct scatterlist *sgl = smi_scatterlist_from_buffer(inst,\n+\t\t\t\t\t\t\t\t      phy_addr,\n+\t\t\t\t\t\t\t\t      n_bytes);\n+\t\tif (!sgl) {\n+\t\t\tsmi_dump_context_labelled(inst,\n+\t\t\t\"Error: could not create scatterlist for read!\");\n+\t\t\tgoto out;\n+\t\t}\n+\t\tsmi_dma_read_sgl(inst, sgl, 1, n_bytes);\n+\t\tdma_unmap_single(inst->dev, phy_addr, n_bytes, DMA_DEV_TO_MEM);\n+\t} else if (n_bytes) {\n+\t\tsmi_read_fifo(inst, (uint32_t *)buf, n_bytes);\n+\t}\n+\tbuf += n_bytes;\n+\n+\tif (inst->settings.data_width == SMI_WIDTH_8BIT) {\n+\t\twhile (odd_bytes--)\n+\t\t\t*((uint8_t *) (buf++)) = smi_read_single_word(inst);\n+\t} else {\n+\t\twhile (odd_bytes >= 2) {\n+\t\t\t*(uint16_t *) buf = smi_read_single_word(inst);\n+\t\t\tbuf += 2;\n+\t\t\todd_bytes -= 2;\n+\t\t}\n+\t\tif (odd_bytes) {\n+\t\t\tdev_err(inst->dev,\n+\t\t\"WARNING: odd number of bytes specified for wide transfer.\");\n+\t\t\tdev_err(inst->dev,\n+\t\t\"At least one byte dropped as a result.\");\n+\t\t\tdump_stack();\n+\t\t}\n+\t}\n+out:\n+\tspin_unlock(&inst->transaction_lock);\n+}\n+EXPORT_SYMBOL(bcm2835_smi_read_buf);\n+\n+void bcm2835_smi_set_address(struct bcm2835_smi_instance *inst,\n+\tunsigned int address)\n+{\n+\tspin_lock(&inst->transaction_lock);\n+\tsmi_set_address(inst, address);\n+\tspin_unlock(&inst->transaction_lock);\n+}\n+EXPORT_SYMBOL(bcm2835_smi_set_address);\n+\n+struct bcm2835_smi_instance *bcm2835_smi_register(struct device_node *node)\n+{\n+\tstruct platform_device *pdev;\n+\n+\tif (!node)\n+\t\treturn NULL;\n+\n+\tpdev = of_find_device_by_node(node);\n+\tif (!pdev)\n+\t\treturn NULL;\n+\n+\treturn platform_get_drvdata(pdev);\n+}\n+EXPORT_SYMBOL(bcm2835_smi_register);\n+\n+void bcm2835_smi_unregister(struct bcm2835_smi_instance *inst)\n+{\n+\t/* Currently nothing needs to be done here, but if we want to e.g.\n+\t   keep track of different sets of settings for different clients\n+\t   or have a more complex concurrency model, it's nice to know when a\n+\t   client is unloaded, so clients should call this function on exit */\n+}\n+EXPORT_SYMBOL(bcm2835_smi_unregister);\n+\n+/****************************************************************************\n+*\n+*   bcm2835_smi_probe - called when the driver is loaded.\n+*\n+***************************************************************************/\n+\n+static int bcm2835_smi_dma_setup(struct bcm2835_smi_instance *inst)\n+{\n+\tint i, rv = 0;\n+\n+\tinst->dma_chan = dma_request_slave_channel(inst->dev, \"chan\");\n+\n+\tinst->dma_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;\n+\tinst->dma_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;\n+\tinst->dma_config.src_addr = inst->smi_regs_busaddr + SMID;\n+\tinst->dma_config.dst_addr = inst->dma_config.src_addr;\n+\t/* Direction unimportant - always overridden by prep_slave_sg */\n+\tinst->dma_config.direction = DMA_DEV_TO_MEM;\n+\tdmaengine_slave_config(inst->dma_chan, &inst->dma_config);\n+\t/* Alloc and map bounce buffers */\n+\tfor (i = 0; i < DMA_BOUNCE_BUFFER_COUNT; ++i) {\n+\t\tinst->bounce.buffer[i] =\n+\t\tdmam_alloc_coherent(inst->dev, DMA_BOUNCE_BUFFER_SIZE,\n+\t\t\t\t&inst->bounce.phys[i],\n+\t\t\t\tGFP_KERNEL);\n+\t\tif (!inst->bounce.buffer[i]) {\n+\t\t\tdev_err(inst->dev, \"Could not allocate buffer!\");\n+\t\t\trv = -ENOMEM;\n+\t\t\tbreak;\n+\t\t}\n+\t\tinst->bounce.sgl[i] = smi_alloc_scatterlist_from_buffer\n+\t\t(\n+\t\t\tinst,\n+\t\t\tinst->bounce.phys[i],\n+\t\t\tDMA_BOUNCE_BUFFER_SIZE\n+\t\t);\n+\t\tif (!inst->bounce.sgl[i]) {\n+\t\t\trv = -ENOMEM;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\treturn rv;\n+}\n+\n+static int bcm2835_smi_probe(struct platform_device *pdev)\n+{\n+\tint err;\n+\tstruct device *dev = &pdev->dev;\n+\tstruct device_node *node = dev->of_node;\n+\tstruct resource *ioresource;\n+\tstruct bcm2835_smi_instance *inst;\n+\n+\t/* Allocate buffers and instance data */\n+\n+\tinst = devm_kzalloc(dev, sizeof(struct bcm2835_smi_instance),\n+\t\tGFP_KERNEL);\n+\n+\tif (!inst)\n+\t\treturn -ENOMEM;\n+\n+\tinst->buffer_sgl = devm_kzalloc(dev,\n+\t\tsizeof(struct scatterlist), GFP_KERNEL);\n+\n+\tif (!inst->buffer_sgl)\n+\t\treturn -ENOMEM;\n+\n+\tinst->dev = dev;\n+\tspin_lock_init(&inst->transaction_lock);\n+\n+\t/* We require device tree support */\n+\n+\tBUG_ON(!node);","path":"drivers/misc/bcm2835_smi.c","position":956,"original_position":956,"commit_id":"d6fbb09d5f3549ecaa1ff825448afab13b4549f3","original_commit_id":"d6fbb09d5f3549ecaa1ff825448afab13b4549f3","user":{"login":"notro","id":530887,"avatar_url":"https://avatars.githubusercontent.com/u/530887?v=3","gravatar_id":"","url":"https://api.github.com/users/notro","html_url":"https://github.com/notro","followers_url":"https://api.github.com/users/notro/followers","following_url":"https://api.github.com/users/notro/following{/other_user}","gists_url":"https://api.github.com/users/notro/gists{/gist_id}","starred_url":"https://api.github.com/users/notro/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/notro/subscriptions","organizations_url":"https://api.github.com/users/notro/orgs","repos_url":"https://api.github.com/users/notro/repos","events_url":"https://api.github.com/users/notro/events{/privacy}","received_events_url":"https://api.github.com/users/notro/received_events","type":"User","site_admin":false},"body":"It's hard to sentence the kernel to death because of this.\r\nFrom source/include/asm-generic/bug.h:\r\n```\r\nDon't use BUG() or BUG_ON() unless there's really no way out\r\n```\r\nMaybe just return -EINVAL? ","created_at":"2015-08-29T18:49:39Z","updated_at":"2015-08-29T18:49:39Z","html_url":"https://github.com/raspberrypi/linux/pull/1105#discussion_r38264787","pull_request_url":"https://api.github.com/repos/raspberrypi/linux/pulls/1105","_links":{"self":{"href":"https://api.github.com/repos/raspberrypi/linux/pulls/comments/38264787"},"html":{"href":"https://github.com/raspberrypi/linux/pull/1105#discussion_r38264787"},"pull_request":{"href":"https://api.github.com/repos/raspberrypi/linux/pulls/1105"}}},"pull_request":{"url":"https://api.github.com/repos/raspberrypi/linux/pulls/1105","id":42641930,"html_url":"https://github.com/raspberrypi/linux/pull/1105","diff_url":"https://github.com/raspberrypi/linux/pull/1105.diff","patch_url":"https://github.com/raspberrypi/linux/pull/1105.patch","issue_url":"https://api.github.com/repos/raspberrypi/linux/issues/1105","number":1105,"state":"open","locked":false,"title":"Add SMI driver","user":{"login":"Wren6991","id":1298595,"avatar_url":"https://avatars.githubusercontent.com/u/1298595?v=3","gravatar_id":"","url":"https://api.github.com/users/Wren6991","html_url":"https://github.com/Wren6991","followers_url":"https://api.github.com/users/Wren6991/followers","following_url":"https://api.github.com/users/Wren6991/following{/other_user}","gists_url":"https://api.github.com/users/Wren6991/gists{/gist_id}","starred_url":"https://api.github.com/users/Wren6991/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Wren6991/subscriptions","organizations_url":"https://api.github.com/users/Wren6991/orgs","repos_url":"https://api.github.com/users/Wren6991/repos","events_url":"https://api.github.com/users/Wren6991/events{/privacy}","received_events_url":"https://api.github.com/users/Wren6991/received_events","type":"User","site_admin":false},"body":"Secondary memory interface is a peripheral on the BCM2835 for talking to devices like parallel-interface LCDs and NAND flash chips.\r\n\r\nThis commit adds a driver for controlling SMI which registers a simple chardev + ioctl interface, and an example NAND driver using the functionality exposed by the SMI driver on the kernel side. It's the intention that we'll eventually be able to boot off of NAND flash, but this needs work on the firmware side.\r\n\r\nIt's very much a work in progress - any suggestions are appreciated!","created_at":"2015-08-17T22:28:42Z","updated_at":"2015-08-29T18:49:39Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3d61798048e5a3603d5bb1b8df051bd9006e827a","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/raspberrypi/linux/pulls/1105/commits","review_comments_url":"https://api.github.com/repos/raspberrypi/linux/pulls/1105/comments","review_comment_url":"https://api.github.com/repos/raspberrypi/linux/pulls/comments{/number}","comments_url":"https://api.github.com/repos/raspberrypi/linux/issues/1105/comments","statuses_url":"https://api.github.com/repos/raspberrypi/linux/statuses/d6fbb09d5f3549ecaa1ff825448afab13b4549f3","head":{"label":"Wren6991:rpi-4.1.y","ref":"rpi-4.1.y","sha":"d6fbb09d5f3549ecaa1ff825448afab13b4549f3","user":{"login":"Wren6991","id":1298595,"avatar_url":"https://avatars.githubusercontent.com/u/1298595?v=3","gravatar_id":"","url":"https://api.github.com/users/Wren6991","html_url":"https://github.com/Wren6991","followers_url":"https://api.github.com/users/Wren6991/followers","following_url":"https://api.github.com/users/Wren6991/following{/other_user}","gists_url":"https://api.github.com/users/Wren6991/gists{/gist_id}","starred_url":"https://api.github.com/users/Wren6991/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Wren6991/subscriptions","organizations_url":"https://api.github.com/users/Wren6991/orgs","repos_url":"https://api.github.com/users/Wren6991/repos","events_url":"https://api.github.com/users/Wren6991/events{/privacy}","received_events_url":"https://api.github.com/users/Wren6991/received_events","type":"User","site_admin":false},"repo":{"id":40837562,"name":"linux","full_name":"Wren6991/linux","owner":{"login":"Wren6991","id":1298595,"avatar_url":"https://avatars.githubusercontent.com/u/1298595?v=3","gravatar_id":"","url":"https://api.github.com/users/Wren6991","html_url":"https://github.com/Wren6991","followers_url":"https://api.github.com/users/Wren6991/followers","following_url":"https://api.github.com/users/Wren6991/following{/other_user}","gists_url":"https://api.github.com/users/Wren6991/gists{/gist_id}","starred_url":"https://api.github.com/users/Wren6991/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Wren6991/subscriptions","organizations_url":"https://api.github.com/users/Wren6991/orgs","repos_url":"https://api.github.com/users/Wren6991/repos","events_url":"https://api.github.com/users/Wren6991/events{/privacy}","received_events_url":"https://api.github.com/users/Wren6991/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/Wren6991/linux","description":"","fork":true,"url":"https://api.github.com/repos/Wren6991/linux","forks_url":"https://api.github.com/repos/Wren6991/linux/forks","keys_url":"https://api.github.com/repos/Wren6991/linux/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Wren6991/linux/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Wren6991/linux/teams","hooks_url":"https://api.github.com/repos/Wren6991/linux/hooks","issue_events_url":"https://api.github.com/repos/Wren6991/linux/issues/events{/number}","events_url":"https://api.github.com/repos/Wren6991/linux/events","assignees_url":"https://api.github.com/repos/Wren6991/linux/assignees{/user}","branches_url":"https://api.github.com/repos/Wren6991/linux/branches{/branch}","tags_url":"https://api.github.com/repos/Wren6991/linux/tags","blobs_url":"https://api.github.com/repos/Wren6991/linux/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Wren6991/linux/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Wren6991/linux/git/refs{/sha}","trees_url":"https://api.github.com/repos/Wren6991/linux/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Wren6991/linux/statuses/{sha}","languages_url":"https://api.github.com/repos/Wren6991/linux/languages","stargazers_url":"https://api.github.com/repos/Wren6991/linux/stargazers","contributors_url":"https://api.github.com/repos/Wren6991/linux/contributors","subscribers_url":"https://api.github.com/repos/Wren6991/linux/subscribers","subscription_url":"https://api.github.com/repos/Wren6991/linux/subscription","commits_url":"https://api.github.com/repos/Wren6991/linux/commits{/sha}","git_commits_url":"https://api.github.com/repos/Wren6991/linux/git/commits{/sha}","comments_url":"https://api.github.com/repos/Wren6991/linux/comments{/number}","issue_comment_url":"https://api.github.com/repos/Wren6991/linux/issues/comments{/number}","contents_url":"https://api.github.com/repos/Wren6991/linux/contents/{+path}","compare_url":"https://api.github.com/repos/Wren6991/linux/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Wren6991/linux/merges","archive_url":"https://api.github.com/repos/Wren6991/linux/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Wren6991/linux/downloads","issues_url":"https://api.github.com/repos/Wren6991/linux/issues{/number}","pulls_url":"https://api.github.com/repos/Wren6991/linux/pulls{/number}","milestones_url":"https://api.github.com/repos/Wren6991/linux/milestones{/number}","notifications_url":"https://api.github.com/repos/Wren6991/linux/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Wren6991/linux/labels{/name}","releases_url":"https://api.github.com/repos/Wren6991/linux/releases{/id}","created_at":"2015-08-16T20:01:42Z","updated_at":"2015-08-16T20:05:50Z","pushed_at":"2015-08-27T19:54:37Z","git_url":"git://github.com/Wren6991/linux.git","ssh_url":"git@github.com:Wren6991/linux.git","clone_url":"https://github.com/Wren6991/linux.git","svn_url":"https://github.com/Wren6991/linux","homepage":"","size":1295659,"stargazers_count":0,"watchers_count":0,"language":"C","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"rpi-4.1.y"}},"base":{"label":"raspberrypi:rpi-4.1.y","ref":"rpi-4.1.y","sha":"1be0d57d19f090fcfcadc45b6b3929f9504fd89a","user":{"login":"raspberrypi","id":1294177,"avatar_url":"https://avatars.githubusercontent.com/u/1294177?v=3","gravatar_id":"","url":"https://api.github.com/users/raspberrypi","html_url":"https://github.com/raspberrypi","followers_url":"https://api.github.com/users/raspberrypi/followers","following_url":"https://api.github.com/users/raspberrypi/following{/other_user}","gists_url":"https://api.github.com/users/raspberrypi/gists{/gist_id}","starred_url":"https://api.github.com/users/raspberrypi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/raspberrypi/subscriptions","organizations_url":"https://api.github.com/users/raspberrypi/orgs","repos_url":"https://api.github.com/users/raspberrypi/repos","events_url":"https://api.github.com/users/raspberrypi/events{/privacy}","received_events_url":"https://api.github.com/users/raspberrypi/received_events","type":"Organization","site_admin":false},"repo":{"id":3199002,"name":"linux","full_name":"raspberrypi/linux","owner":{"login":"raspberrypi","id":1294177,"avatar_url":"https://avatars.githubusercontent.com/u/1294177?v=3","gravatar_id":"","url":"https://api.github.com/users/raspberrypi","html_url":"https://github.com/raspberrypi","followers_url":"https://api.github.com/users/raspberrypi/followers","following_url":"https://api.github.com/users/raspberrypi/following{/other_user}","gists_url":"https://api.github.com/users/raspberrypi/gists{/gist_id}","starred_url":"https://api.github.com/users/raspberrypi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/raspberrypi/subscriptions","organizations_url":"https://api.github.com/users/raspberrypi/orgs","repos_url":"https://api.github.com/users/raspberrypi/repos","events_url":"https://api.github.com/users/raspberrypi/events{/privacy}","received_events_url":"https://api.github.com/users/raspberrypi/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/raspberrypi/linux","description":"","fork":false,"url":"https://api.github.com/repos/raspberrypi/linux","forks_url":"https://api.github.com/repos/raspberrypi/linux/forks","keys_url":"https://api.github.com/repos/raspberrypi/linux/keys{/key_id}","collaborators_url":"https://api.github.com/repos/raspberrypi/linux/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/raspberrypi/linux/teams","hooks_url":"https://api.github.com/repos/raspberrypi/linux/hooks","issue_events_url":"https://api.github.com/repos/raspberrypi/linux/issues/events{/number}","events_url":"https://api.github.com/repos/raspberrypi/linux/events","assignees_url":"https://api.github.com/repos/raspberrypi/linux/assignees{/user}","branches_url":"https://api.github.com/repos/raspberrypi/linux/branches{/branch}","tags_url":"https://api.github.com/repos/raspberrypi/linux/tags","blobs_url":"https://api.github.com/repos/raspberrypi/linux/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/raspberrypi/linux/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/raspberrypi/linux/git/refs{/sha}","trees_url":"https://api.github.com/repos/raspberrypi/linux/git/trees{/sha}","statuses_url":"https://api.github.com/repos/raspberrypi/linux/statuses/{sha}","languages_url":"https://api.github.com/repos/raspberrypi/linux/languages","stargazers_url":"https://api.github.com/repos/raspberrypi/linux/stargazers","contributors_url":"https://api.github.com/repos/raspberrypi/linux/contributors","subscribers_url":"https://api.github.com/repos/raspberrypi/linux/subscribers","subscription_url":"https://api.github.com/repos/raspberrypi/linux/subscription","commits_url":"https://api.github.com/repos/raspberrypi/linux/commits{/sha}","git_commits_url":"https://api.github.com/repos/raspberrypi/linux/git/commits{/sha}","comments_url":"https://api.github.com/repos/raspberrypi/linux/comments{/number}","issue_comment_url":"https://api.github.com/repos/raspberrypi/linux/issues/comments{/number}","contents_url":"https://api.github.com/repos/raspberrypi/linux/contents/{+path}","compare_url":"https://api.github.com/repos/raspberrypi/linux/compare/{base}...{head}","merges_url":"https://api.github.com/repos/raspberrypi/linux/merges","archive_url":"https://api.github.com/repos/raspberrypi/linux/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/raspberrypi/linux/downloads","issues_url":"https://api.github.com/repos/raspberrypi/linux/issues{/number}","pulls_url":"https://api.github.com/repos/raspberrypi/linux/pulls{/number}","milestones_url":"https://api.github.com/repos/raspberrypi/linux/milestones{/number}","notifications_url":"https://api.github.com/repos/raspberrypi/linux/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/raspberrypi/linux/labels{/name}","releases_url":"https://api.github.com/repos/raspberrypi/linux/releases{/id}","created_at":"2012-01-17T12:10:20Z","updated_at":"2015-08-28T14:16:27Z","pushed_at":"2015-08-27T19:54:39Z","git_url":"git://github.com/raspberrypi/linux.git","ssh_url":"git@github.com:raspberrypi/linux.git","clone_url":"https://github.com/raspberrypi/linux.git","svn_url":"https://github.com/raspberrypi/linux","homepage":"","size":2431791,"stargazers_count":2308,"watchers_count":2308,"language":"C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1193,"mirror_url":null,"open_issues_count":172,"forks":1193,"open_issues":172,"watchers":2308,"default_branch":"rpi-4.1.y"}},"_links":{"self":{"href":"https://api.github.com/repos/raspberrypi/linux/pulls/1105"},"html":{"href":"https://github.com/raspberrypi/linux/pull/1105"},"issue":{"href":"https://api.github.com/repos/raspberrypi/linux/issues/1105"},"comments":{"href":"https://api.github.com/repos/raspberrypi/linux/issues/1105/comments"},"review_comments":{"href":"https://api.github.com/repos/raspberrypi/linux/pulls/1105/comments"},"review_comment":{"href":"https://api.github.com/repos/raspberrypi/linux/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/raspberrypi/linux/pulls/1105/commits"},"statuses":{"href":"https://api.github.com/repos/raspberrypi/linux/statuses/d6fbb09d5f3549ecaa1ff825448afab13b4549f3"}}}}