linux/drivers/mtd/maps/lantiq-flash.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *
   4 *  Copyright (C) 2004 Liu Peng Infineon IFAP DC COM CPE
   5 *  Copyright (C) 2010 John Crispin <john@phrozen.org>
   6 */
   7
   8#include <linux/err.h>
   9#include <linux/module.h>
  10#include <linux/types.h>
  11#include <linux/kernel.h>
  12#include <linux/io.h>
  13#include <linux/slab.h>
  14#include <linux/mtd/mtd.h>
  15#include <linux/mtd/map.h>
  16#include <linux/mtd/partitions.h>
  17#include <linux/mtd/cfi.h>
  18#include <linux/platform_device.h>
  19#include <linux/mtd/physmap.h>
  20#include <linux/of.h>
  21
  22#include <lantiq_soc.h>
  23
  24/*
  25 * The NOR flash is connected to the same external bus unit (EBU) as PCI.
  26 * To make PCI work we need to enable the endianness swapping for the address
  27 * written to the EBU. This endianness swapping works for PCI correctly but
  28 * fails for attached NOR devices. To workaround this we need to use a complex
  29 * map. The workaround involves swapping all addresses whilst probing the chip.
  30 * Once probing is complete we stop swapping the addresses but swizzle the
  31 * unlock addresses to ensure that access to the NOR device works correctly.
  32 */
  33
  34enum {
  35        LTQ_NOR_PROBING,
  36        LTQ_NOR_NORMAL
  37};
  38
  39struct ltq_mtd {
  40        struct resource *res;
  41        struct mtd_info *mtd;
  42        struct map_info *map;
  43};
  44
  45static const char ltq_map_name[] = "ltq_nor";
  46
  47static map_word
  48ltq_read16(struct map_info *map, unsigned long adr)
  49{
  50        unsigned long flags;
  51        map_word temp;
  52
  53        if (map->map_priv_1 == LTQ_NOR_PROBING)
  54                adr ^= 2;
  55        spin_lock_irqsave(&ebu_lock, flags);
  56        temp.x[0] = *(u16 *)(map->virt + adr);
  57        spin_unlock_irqrestore(&ebu_lock, flags);
  58        return temp;
  59}
  60
  61static void
  62ltq_write16(struct map_info *map, map_word d, unsigned long adr)
  63{
  64        unsigned long flags;
  65
  66        if (map->map_priv_1 == LTQ_NOR_PROBING)
  67                adr ^= 2;
  68        spin_lock_irqsave(&ebu_lock, flags);
  69        *(u16 *)(map->virt + adr) = d.x[0];
  70        spin_unlock_irqrestore(&ebu_lock, flags);
  71}
  72
  73/*
  74 * The following 2 functions copy data between iomem and a cached memory
  75 * section. As memcpy() makes use of pre-fetching we cannot use it here.
  76 * The normal alternative of using memcpy_{to,from}io also makes use of
  77 * memcpy() on MIPS so it is not applicable either. We are therefore stuck
  78 * with having to use our own loop.
  79 */
  80static void
  81ltq_copy_from(struct map_info *map, void *to,
  82        unsigned long from, ssize_t len)
  83{
  84        unsigned char *f = (unsigned char *)map->virt + from;
  85        unsigned char *t = (unsigned char *)to;
  86        unsigned long flags;
  87
  88        spin_lock_irqsave(&ebu_lock, flags);
  89        while (len--)
  90                *t++ = *f++;
  91        spin_unlock_irqrestore(&ebu_lock, flags);
  92}
  93
  94static void
  95ltq_copy_to(struct map_info *map, unsigned long to,
  96        const void *from, ssize_t len)
  97{
  98        unsigned char *f = (unsigned char *)from;
  99        unsigned char *t = (unsigned char *)map->virt + to;
 100        unsigned long flags;
 101
 102        spin_lock_irqsave(&ebu_lock, flags);
 103        while (len--)
 104                *t++ = *f++;
 105        spin_unlock_irqrestore(&ebu_lock, flags);
 106}
 107
 108static int
 109ltq_mtd_probe(struct platform_device *pdev)
 110{
 111        struct ltq_mtd *ltq_mtd;
 112        struct cfi_private *cfi;
 113        int err;
 114
 115        ltq_mtd = devm_kzalloc(&pdev->dev, sizeof(struct ltq_mtd), GFP_KERNEL);
 116        if (!ltq_mtd)
 117                return -ENOMEM;
 118
 119        platform_set_drvdata(pdev, ltq_mtd);
 120
 121        ltq_mtd->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 122        if (!ltq_mtd->res) {
 123                dev_err(&pdev->dev, "failed to get memory resource\n");
 124                return -ENOENT;
 125        }
 126
 127        ltq_mtd->map = devm_kzalloc(&pdev->dev, sizeof(struct map_info),
 128                                    GFP_KERNEL);
 129        if (!ltq_mtd->map)
 130                return -ENOMEM;
 131
 132        ltq_mtd->map->phys = ltq_mtd->res->start;
 133        ltq_mtd->map->size = resource_size(ltq_mtd->res);
 134        ltq_mtd->map->virt = devm_ioremap_resource(&pdev->dev, ltq_mtd->res);
 135        if (IS_ERR(ltq_mtd->map->virt))
 136                return PTR_ERR(ltq_mtd->map->virt);
 137
 138        ltq_mtd->map->name = ltq_map_name;
 139        ltq_mtd->map->bankwidth = 2;
 140        ltq_mtd->map->read = ltq_read16;
 141        ltq_mtd->map->write = ltq_write16;
 142        ltq_mtd->map->copy_from = ltq_copy_from;
 143        ltq_mtd->map->copy_to = ltq_copy_to;
 144
 145        ltq_mtd->map->map_priv_1 = LTQ_NOR_PROBING;
 146        ltq_mtd->mtd = do_map_probe("cfi_probe", ltq_mtd->map);
 147        ltq_mtd->map->map_priv_1 = LTQ_NOR_NORMAL;
 148
 149        if (!ltq_mtd->mtd) {
 150                dev_err(&pdev->dev, "probing failed\n");
 151                return -ENXIO;
 152        }
 153
 154        ltq_mtd->mtd->dev.parent = &pdev->dev;
 155        mtd_set_of_node(ltq_mtd->mtd, pdev->dev.of_node);
 156
 157        cfi = ltq_mtd->map->fldrv_priv;
 158        cfi->addr_unlock1 ^= 1;
 159        cfi->addr_unlock2 ^= 1;
 160
 161        err = mtd_device_register(ltq_mtd->mtd, NULL, 0);
 162        if (err) {
 163                dev_err(&pdev->dev, "failed to add partitions\n");
 164                goto err_destroy;
 165        }
 166
 167        return 0;
 168
 169err_destroy:
 170        map_destroy(ltq_mtd->mtd);
 171        return err;
 172}
 173
 174static int
 175ltq_mtd_remove(struct platform_device *pdev)
 176{
 177        struct ltq_mtd *ltq_mtd = platform_get_drvdata(pdev);
 178
 179        if (ltq_mtd && ltq_mtd->mtd) {
 180                mtd_device_unregister(ltq_mtd->mtd);
 181                map_destroy(ltq_mtd->mtd);
 182        }
 183        return 0;
 184}
 185
 186static const struct of_device_id ltq_mtd_match[] = {
 187        { .compatible = "lantiq,nor" },
 188        {},
 189};
 190MODULE_DEVICE_TABLE(of, ltq_mtd_match);
 191
 192static struct platform_driver ltq_mtd_driver = {
 193        .probe = ltq_mtd_probe,
 194        .remove = ltq_mtd_remove,
 195        .driver = {
 196                .name = "ltq-nor",
 197                .of_match_table = ltq_mtd_match,
 198        },
 199};
 200
 201module_platform_driver(ltq_mtd_driver);
 202
 203MODULE_LICENSE("GPL");
 204MODULE_AUTHOR("John Crispin <john@phrozen.org>");
 205MODULE_DESCRIPTION("Lantiq SoC NOR");
 206