c942fddf8793b (Thomas Gleixner 2019-05-27 08:55:06 +0200 1) // SPDX-License-Identifier: GPL-2.0-or-later
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 2) /*
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 3) * Freescale MXS On-Chip OTP driver
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 4) *
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 5) * Copyright (C) 2015 Stefan Wahren <stefan.wahren@i2se.com>
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 6) *
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 7) * Based on the driver from Huang Shijie and Christoph G. Baumann
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 8) */
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 9) #include <linux/clk.h>
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 10) #include <linux/delay.h>
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 11) #include <linux/device.h>
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 12) #include <linux/err.h>
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 13) #include <linux/io.h>
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 14) #include <linux/module.h>
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 15) #include <linux/nvmem-provider.h>
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 16) #include <linux/of_device.h>
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 17) #include <linux/platform_device.h>
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 18) #include <linux/slab.h>
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 19) #include <linux/stmp_device.h>
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 20)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 21) /* OCOTP registers and bits */
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 22)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 23) #define BM_OCOTP_CTRL_RD_BANK_OPEN BIT(12)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 24) #define BM_OCOTP_CTRL_ERROR BIT(9)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 25) #define BM_OCOTP_CTRL_BUSY BIT(8)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 26)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 27) #define OCOTP_TIMEOUT 10000
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 28) #define OCOTP_DATA_OFFSET 0x20
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 29)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 30) struct mxs_ocotp {
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 31) struct clk *clk;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 32) void __iomem *base;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 33) struct nvmem_device *nvmem;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 34) };
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 35)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 36) static int mxs_ocotp_wait(struct mxs_ocotp *otp)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 37) {
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 38) int timeout = OCOTP_TIMEOUT;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 39) unsigned int status = 0;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 40)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 41) while (timeout--) {
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 42) status = readl(otp->base);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 43)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 44) if (!(status & (BM_OCOTP_CTRL_BUSY | BM_OCOTP_CTRL_ERROR)))
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 45) break;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 46)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 47) cpu_relax();
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 48) }
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 49)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 50) if (status & BM_OCOTP_CTRL_BUSY)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 51) return -EBUSY;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 52) else if (status & BM_OCOTP_CTRL_ERROR)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 53) return -EIO;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 54)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 55) return 0;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 56) }
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 57)
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 58) static int mxs_ocotp_read(void *context, unsigned int offset,
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 59) void *val, size_t bytes)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 60) {
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 61) struct mxs_ocotp *otp = context;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 62) u32 *buf = val;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 63) int ret;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 64)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 65) ret = clk_enable(otp->clk);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 66) if (ret)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 67) return ret;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 68)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 69) writel(BM_OCOTP_CTRL_ERROR, otp->base + STMP_OFFSET_REG_CLR);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 70)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 71) ret = mxs_ocotp_wait(otp);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 72) if (ret)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 73) goto disable_clk;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 74)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 75) /* open OCOTP banks for read */
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 76) writel(BM_OCOTP_CTRL_RD_BANK_OPEN, otp->base + STMP_OFFSET_REG_SET);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 77)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 78) /* approximately wait 33 hclk cycles */
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 79) udelay(1);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 80)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 81) ret = mxs_ocotp_wait(otp);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 82) if (ret)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 83) goto close_banks;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 84)
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 85) while (bytes) {
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 86) if ((offset < OCOTP_DATA_OFFSET) || (offset % 16)) {
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 87) /* fill up non-data register */
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 88) *buf++ = 0;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 89) } else {
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 90) *buf++ = readl(otp->base + offset);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 91) }
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 92)
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 93) bytes -= 4;
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 94) offset += 4;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 95) }
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 96)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 97) close_banks:
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 98) /* close banks for power saving */
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 99) writel(BM_OCOTP_CTRL_RD_BANK_OPEN, otp->base + STMP_OFFSET_REG_CLR);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 100)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 101) disable_clk:
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 102) clk_disable(otp->clk);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 103)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 104) return ret;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 105) }
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 106)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 107) static struct nvmem_config ocotp_config = {
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 108) .name = "mxs-ocotp",
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 109) .stride = 16,
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 110) .word_size = 4,
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 111) .reg_read = mxs_ocotp_read,
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 112) };
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 113)
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 114) struct mxs_data {
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 115) int size;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 116) };
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 117)
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 118) static const struct mxs_data imx23_data = {
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 119) .size = 0x220,
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 120) };
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 121)
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 122) static const struct mxs_data imx28_data = {
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 123) .size = 0x2a0,
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 124) };
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 125)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 126) static const struct of_device_id mxs_ocotp_match[] = {
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 127) { .compatible = "fsl,imx23-ocotp", .data = &imx23_data },
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 128) { .compatible = "fsl,imx28-ocotp", .data = &imx28_data },
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 129) { /* sentinel */},
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 130) };
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 131) MODULE_DEVICE_TABLE(of, mxs_ocotp_match);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 132)
bbde5709ee4f6 (Anson Huang 2020-03-23 15:00:06 +0000 133) static void mxs_ocotp_action(void *data)
bbde5709ee4f6 (Anson Huang 2020-03-23 15:00:06 +0000 134) {
bbde5709ee4f6 (Anson Huang 2020-03-23 15:00:06 +0000 135) clk_unprepare(data);
bbde5709ee4f6 (Anson Huang 2020-03-23 15:00:06 +0000 136) }
bbde5709ee4f6 (Anson Huang 2020-03-23 15:00:06 +0000 137)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 138) static int mxs_ocotp_probe(struct platform_device *pdev)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 139) {
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 140) struct device *dev = &pdev->dev;
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 141) const struct mxs_data *data;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 142) struct mxs_ocotp *otp;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 143) const struct of_device_id *match;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 144) int ret;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 145)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 146) match = of_match_device(dev->driver->of_match_table, dev);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 147) if (!match || !match->data)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 148) return -EINVAL;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 149)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 150) otp = devm_kzalloc(dev, sizeof(*otp), GFP_KERNEL);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 151) if (!otp)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 152) return -ENOMEM;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 153)
794a1e229b92a (Anson Huang 2019-04-13 11:33:00 +0100 154) otp->base = devm_platform_ioremap_resource(pdev, 0);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 155) if (IS_ERR(otp->base))
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 156) return PTR_ERR(otp->base);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 157)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 158) otp->clk = devm_clk_get(&pdev->dev, NULL);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 159) if (IS_ERR(otp->clk))
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 160) return PTR_ERR(otp->clk);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 161)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 162) ret = clk_prepare(otp->clk);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 163) if (ret < 0) {
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 164) dev_err(dev, "failed to prepare clk: %d\n", ret);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 165) return ret;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 166) }
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 167)
bbde5709ee4f6 (Anson Huang 2020-03-23 15:00:06 +0000 168) ret = devm_add_action_or_reset(&pdev->dev, mxs_ocotp_action, otp->clk);
bbde5709ee4f6 (Anson Huang 2020-03-23 15:00:06 +0000 169) if (ret)
bbde5709ee4f6 (Anson Huang 2020-03-23 15:00:06 +0000 170) return ret;
bbde5709ee4f6 (Anson Huang 2020-03-23 15:00:06 +0000 171)
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 172) data = match->data;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 173)
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 174) ocotp_config.size = data->size;
7d8867d71fba7 (Srinivas Kandagatla 2016-05-02 19:36:14 +0100 175) ocotp_config.priv = otp;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 176) ocotp_config.dev = dev;
7d9f9f24fac77 (Bartosz Golaszewski 2018-09-21 06:40:12 -0700 177) otp->nvmem = devm_nvmem_register(dev, &ocotp_config);
bbde5709ee4f6 (Anson Huang 2020-03-23 15:00:06 +0000 178) if (IS_ERR(otp->nvmem))
bbde5709ee4f6 (Anson Huang 2020-03-23 15:00:06 +0000 179) return PTR_ERR(otp->nvmem);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 180)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 181) platform_set_drvdata(pdev, otp);
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 182)
7d9f9f24fac77 (Bartosz Golaszewski 2018-09-21 06:40:12 -0700 183) return 0;
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 184) }
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 185)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 186) static struct platform_driver mxs_ocotp_driver = {
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 187) .probe = mxs_ocotp_probe,
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 188) .driver = {
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 189) .name = "mxs-ocotp",
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 190) .of_match_table = mxs_ocotp_match,
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 191) },
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 192) };
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 193)
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 194) module_platform_driver(mxs_ocotp_driver);
5fb812293ad67 (Stefan Wahren 2019-08-18 10:33:43 +0100 195) MODULE_AUTHOR("Stefan Wahren <wahrenst@gmx.net");
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 196) MODULE_DESCRIPTION("driver for OCOTP in i.MX23/i.MX28");
c01e9a11ab6f3 (Stefan Wahren 2015-09-30 13:56:27 +0100 197) MODULE_LICENSE("GPL v2");