1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * LPDDR2-NVM MTD driver. This module provides read, write, erase, lock/unlock
4*4882a593Smuzhiyun * support for LPDDR2-NVM PCM memories
5*4882a593Smuzhiyun *
6*4882a593Smuzhiyun * Copyright © 2012 Micron Technology, Inc.
7*4882a593Smuzhiyun *
8*4882a593Smuzhiyun * Vincenzo Aliberti <vincenzo.aliberti@gmail.com>
9*4882a593Smuzhiyun * Domenico Manna <domenico.manna@gmail.com>
10*4882a593Smuzhiyun * Many thanks to Andrea Vigilante for initial enabling
11*4882a593Smuzhiyun */
12*4882a593Smuzhiyun
13*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
14*4882a593Smuzhiyun
15*4882a593Smuzhiyun #include <linux/init.h>
16*4882a593Smuzhiyun #include <linux/io.h>
17*4882a593Smuzhiyun #include <linux/module.h>
18*4882a593Smuzhiyun #include <linux/kernel.h>
19*4882a593Smuzhiyun #include <linux/mtd/map.h>
20*4882a593Smuzhiyun #include <linux/mtd/mtd.h>
21*4882a593Smuzhiyun #include <linux/mtd/partitions.h>
22*4882a593Smuzhiyun #include <linux/slab.h>
23*4882a593Smuzhiyun #include <linux/platform_device.h>
24*4882a593Smuzhiyun #include <linux/ioport.h>
25*4882a593Smuzhiyun #include <linux/err.h>
26*4882a593Smuzhiyun
27*4882a593Smuzhiyun /* Parameters */
28*4882a593Smuzhiyun #define ERASE_BLOCKSIZE (0x00020000/2) /* in Word */
29*4882a593Smuzhiyun #define WRITE_BUFFSIZE (0x00000400/2) /* in Word */
30*4882a593Smuzhiyun #define OW_BASE_ADDRESS 0x00000000 /* OW offset */
31*4882a593Smuzhiyun #define BUS_WIDTH 0x00000020 /* x32 devices */
32*4882a593Smuzhiyun
33*4882a593Smuzhiyun /* PFOW symbols address offset */
34*4882a593Smuzhiyun #define PFOW_QUERY_STRING_P (0x0000/2) /* in Word */
35*4882a593Smuzhiyun #define PFOW_QUERY_STRING_F (0x0002/2) /* in Word */
36*4882a593Smuzhiyun #define PFOW_QUERY_STRING_O (0x0004/2) /* in Word */
37*4882a593Smuzhiyun #define PFOW_QUERY_STRING_W (0x0006/2) /* in Word */
38*4882a593Smuzhiyun
39*4882a593Smuzhiyun /* OW registers address */
40*4882a593Smuzhiyun #define CMD_CODE_OFS (0x0080/2) /* in Word */
41*4882a593Smuzhiyun #define CMD_DATA_OFS (0x0084/2) /* in Word */
42*4882a593Smuzhiyun #define CMD_ADD_L_OFS (0x0088/2) /* in Word */
43*4882a593Smuzhiyun #define CMD_ADD_H_OFS (0x008A/2) /* in Word */
44*4882a593Smuzhiyun #define MPR_L_OFS (0x0090/2) /* in Word */
45*4882a593Smuzhiyun #define MPR_H_OFS (0x0092/2) /* in Word */
46*4882a593Smuzhiyun #define CMD_EXEC_OFS (0x00C0/2) /* in Word */
47*4882a593Smuzhiyun #define STATUS_REG_OFS (0x00CC/2) /* in Word */
48*4882a593Smuzhiyun #define PRG_BUFFER_OFS (0x0010/2) /* in Word */
49*4882a593Smuzhiyun
50*4882a593Smuzhiyun /* Datamask */
51*4882a593Smuzhiyun #define MR_CFGMASK 0x8000
52*4882a593Smuzhiyun #define SR_OK_DATAMASK 0x0080
53*4882a593Smuzhiyun
54*4882a593Smuzhiyun /* LPDDR2-NVM Commands */
55*4882a593Smuzhiyun #define LPDDR2_NVM_LOCK 0x0061
56*4882a593Smuzhiyun #define LPDDR2_NVM_UNLOCK 0x0062
57*4882a593Smuzhiyun #define LPDDR2_NVM_SW_PROGRAM 0x0041
58*4882a593Smuzhiyun #define LPDDR2_NVM_SW_OVERWRITE 0x0042
59*4882a593Smuzhiyun #define LPDDR2_NVM_BUF_PROGRAM 0x00E9
60*4882a593Smuzhiyun #define LPDDR2_NVM_BUF_OVERWRITE 0x00EA
61*4882a593Smuzhiyun #define LPDDR2_NVM_ERASE 0x0020
62*4882a593Smuzhiyun
63*4882a593Smuzhiyun /* LPDDR2-NVM Registers offset */
64*4882a593Smuzhiyun #define LPDDR2_MODE_REG_DATA 0x0040
65*4882a593Smuzhiyun #define LPDDR2_MODE_REG_CFG 0x0050
66*4882a593Smuzhiyun
67*4882a593Smuzhiyun /*
68*4882a593Smuzhiyun * Internal Type Definitions
69*4882a593Smuzhiyun * pcm_int_data contains memory controller details:
70*4882a593Smuzhiyun * @reg_data : LPDDR2_MODE_REG_DATA register address after remapping
71*4882a593Smuzhiyun * @reg_cfg : LPDDR2_MODE_REG_CFG register address after remapping
72*4882a593Smuzhiyun * &bus_width: memory bus-width (eg: x16 2 Bytes, x32 4 Bytes)
73*4882a593Smuzhiyun */
74*4882a593Smuzhiyun struct pcm_int_data {
75*4882a593Smuzhiyun void __iomem *ctl_regs;
76*4882a593Smuzhiyun int bus_width;
77*4882a593Smuzhiyun };
78*4882a593Smuzhiyun
79*4882a593Smuzhiyun static DEFINE_MUTEX(lpdd2_nvm_mutex);
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun /*
82*4882a593Smuzhiyun * Build a map_word starting from an u_long
83*4882a593Smuzhiyun */
build_map_word(u_long myword)84*4882a593Smuzhiyun static inline map_word build_map_word(u_long myword)
85*4882a593Smuzhiyun {
86*4882a593Smuzhiyun map_word val = { {0} };
87*4882a593Smuzhiyun val.x[0] = myword;
88*4882a593Smuzhiyun return val;
89*4882a593Smuzhiyun }
90*4882a593Smuzhiyun
91*4882a593Smuzhiyun /*
92*4882a593Smuzhiyun * Build Mode Register Configuration DataMask based on device bus-width
93*4882a593Smuzhiyun */
build_mr_cfgmask(u_int bus_width)94*4882a593Smuzhiyun static inline u_int build_mr_cfgmask(u_int bus_width)
95*4882a593Smuzhiyun {
96*4882a593Smuzhiyun u_int val = MR_CFGMASK;
97*4882a593Smuzhiyun
98*4882a593Smuzhiyun if (bus_width == 0x0004) /* x32 device */
99*4882a593Smuzhiyun val = val << 16;
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun return val;
102*4882a593Smuzhiyun }
103*4882a593Smuzhiyun
104*4882a593Smuzhiyun /*
105*4882a593Smuzhiyun * Build Status Register OK DataMask based on device bus-width
106*4882a593Smuzhiyun */
build_sr_ok_datamask(u_int bus_width)107*4882a593Smuzhiyun static inline u_int build_sr_ok_datamask(u_int bus_width)
108*4882a593Smuzhiyun {
109*4882a593Smuzhiyun u_int val = SR_OK_DATAMASK;
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun if (bus_width == 0x0004) /* x32 device */
112*4882a593Smuzhiyun val = (val << 16)+val;
113*4882a593Smuzhiyun
114*4882a593Smuzhiyun return val;
115*4882a593Smuzhiyun }
116*4882a593Smuzhiyun
117*4882a593Smuzhiyun /*
118*4882a593Smuzhiyun * Evaluates Overlay Window Control Registers address
119*4882a593Smuzhiyun */
ow_reg_add(struct map_info * map,u_long offset)120*4882a593Smuzhiyun static inline u_long ow_reg_add(struct map_info *map, u_long offset)
121*4882a593Smuzhiyun {
122*4882a593Smuzhiyun u_long val = 0;
123*4882a593Smuzhiyun struct pcm_int_data *pcm_data = map->fldrv_priv;
124*4882a593Smuzhiyun
125*4882a593Smuzhiyun val = map->pfow_base + offset*pcm_data->bus_width;
126*4882a593Smuzhiyun
127*4882a593Smuzhiyun return val;
128*4882a593Smuzhiyun }
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun /*
131*4882a593Smuzhiyun * Enable lpddr2-nvm Overlay Window
132*4882a593Smuzhiyun * Overlay Window is a memory mapped area containing all LPDDR2-NVM registers
133*4882a593Smuzhiyun * used by device commands as well as uservisible resources like Device Status
134*4882a593Smuzhiyun * Register, Device ID, etc
135*4882a593Smuzhiyun */
ow_enable(struct map_info * map)136*4882a593Smuzhiyun static inline void ow_enable(struct map_info *map)
137*4882a593Smuzhiyun {
138*4882a593Smuzhiyun struct pcm_int_data *pcm_data = map->fldrv_priv;
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun writel_relaxed(build_mr_cfgmask(pcm_data->bus_width) | 0x18,
141*4882a593Smuzhiyun pcm_data->ctl_regs + LPDDR2_MODE_REG_CFG);
142*4882a593Smuzhiyun writel_relaxed(0x01, pcm_data->ctl_regs + LPDDR2_MODE_REG_DATA);
143*4882a593Smuzhiyun }
144*4882a593Smuzhiyun
145*4882a593Smuzhiyun /*
146*4882a593Smuzhiyun * Disable lpddr2-nvm Overlay Window
147*4882a593Smuzhiyun * Overlay Window is a memory mapped area containing all LPDDR2-NVM registers
148*4882a593Smuzhiyun * used by device commands as well as uservisible resources like Device Status
149*4882a593Smuzhiyun * Register, Device ID, etc
150*4882a593Smuzhiyun */
ow_disable(struct map_info * map)151*4882a593Smuzhiyun static inline void ow_disable(struct map_info *map)
152*4882a593Smuzhiyun {
153*4882a593Smuzhiyun struct pcm_int_data *pcm_data = map->fldrv_priv;
154*4882a593Smuzhiyun
155*4882a593Smuzhiyun writel_relaxed(build_mr_cfgmask(pcm_data->bus_width) | 0x18,
156*4882a593Smuzhiyun pcm_data->ctl_regs + LPDDR2_MODE_REG_CFG);
157*4882a593Smuzhiyun writel_relaxed(0x02, pcm_data->ctl_regs + LPDDR2_MODE_REG_DATA);
158*4882a593Smuzhiyun }
159*4882a593Smuzhiyun
160*4882a593Smuzhiyun /*
161*4882a593Smuzhiyun * Execute lpddr2-nvm operations
162*4882a593Smuzhiyun */
lpddr2_nvm_do_op(struct map_info * map,u_long cmd_code,u_long cmd_data,u_long cmd_add,u_long cmd_mpr,u_char * buf)163*4882a593Smuzhiyun static int lpddr2_nvm_do_op(struct map_info *map, u_long cmd_code,
164*4882a593Smuzhiyun u_long cmd_data, u_long cmd_add, u_long cmd_mpr, u_char *buf)
165*4882a593Smuzhiyun {
166*4882a593Smuzhiyun map_word add_l = { {0} }, add_h = { {0} }, mpr_l = { {0} },
167*4882a593Smuzhiyun mpr_h = { {0} }, data_l = { {0} }, cmd = { {0} },
168*4882a593Smuzhiyun exec_cmd = { {0} }, sr;
169*4882a593Smuzhiyun map_word data_h = { {0} }; /* only for 2x x16 devices stacked */
170*4882a593Smuzhiyun u_long i, status_reg, prg_buff_ofs;
171*4882a593Smuzhiyun struct pcm_int_data *pcm_data = map->fldrv_priv;
172*4882a593Smuzhiyun u_int sr_ok_datamask = build_sr_ok_datamask(pcm_data->bus_width);
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun /* Builds low and high words for OW Control Registers */
175*4882a593Smuzhiyun add_l.x[0] = cmd_add & 0x0000FFFF;
176*4882a593Smuzhiyun add_h.x[0] = (cmd_add >> 16) & 0x0000FFFF;
177*4882a593Smuzhiyun mpr_l.x[0] = cmd_mpr & 0x0000FFFF;
178*4882a593Smuzhiyun mpr_h.x[0] = (cmd_mpr >> 16) & 0x0000FFFF;
179*4882a593Smuzhiyun cmd.x[0] = cmd_code & 0x0000FFFF;
180*4882a593Smuzhiyun exec_cmd.x[0] = 0x0001;
181*4882a593Smuzhiyun data_l.x[0] = cmd_data & 0x0000FFFF;
182*4882a593Smuzhiyun data_h.x[0] = (cmd_data >> 16) & 0x0000FFFF; /* only for 2x x16 */
183*4882a593Smuzhiyun
184*4882a593Smuzhiyun /* Set Overlay Window Control Registers */
185*4882a593Smuzhiyun map_write(map, cmd, ow_reg_add(map, CMD_CODE_OFS));
186*4882a593Smuzhiyun map_write(map, data_l, ow_reg_add(map, CMD_DATA_OFS));
187*4882a593Smuzhiyun map_write(map, add_l, ow_reg_add(map, CMD_ADD_L_OFS));
188*4882a593Smuzhiyun map_write(map, add_h, ow_reg_add(map, CMD_ADD_H_OFS));
189*4882a593Smuzhiyun map_write(map, mpr_l, ow_reg_add(map, MPR_L_OFS));
190*4882a593Smuzhiyun map_write(map, mpr_h, ow_reg_add(map, MPR_H_OFS));
191*4882a593Smuzhiyun if (pcm_data->bus_width == 0x0004) { /* 2x16 devices stacked */
192*4882a593Smuzhiyun map_write(map, cmd, ow_reg_add(map, CMD_CODE_OFS) + 2);
193*4882a593Smuzhiyun map_write(map, data_h, ow_reg_add(map, CMD_DATA_OFS) + 2);
194*4882a593Smuzhiyun map_write(map, add_l, ow_reg_add(map, CMD_ADD_L_OFS) + 2);
195*4882a593Smuzhiyun map_write(map, add_h, ow_reg_add(map, CMD_ADD_H_OFS) + 2);
196*4882a593Smuzhiyun map_write(map, mpr_l, ow_reg_add(map, MPR_L_OFS) + 2);
197*4882a593Smuzhiyun map_write(map, mpr_h, ow_reg_add(map, MPR_H_OFS) + 2);
198*4882a593Smuzhiyun }
199*4882a593Smuzhiyun
200*4882a593Smuzhiyun /* Fill Program Buffer */
201*4882a593Smuzhiyun if ((cmd_code == LPDDR2_NVM_BUF_PROGRAM) ||
202*4882a593Smuzhiyun (cmd_code == LPDDR2_NVM_BUF_OVERWRITE)) {
203*4882a593Smuzhiyun prg_buff_ofs = (map_read(map,
204*4882a593Smuzhiyun ow_reg_add(map, PRG_BUFFER_OFS))).x[0];
205*4882a593Smuzhiyun for (i = 0; i < cmd_mpr; i++) {
206*4882a593Smuzhiyun map_write(map, build_map_word(buf[i]), map->pfow_base +
207*4882a593Smuzhiyun prg_buff_ofs + i);
208*4882a593Smuzhiyun }
209*4882a593Smuzhiyun }
210*4882a593Smuzhiyun
211*4882a593Smuzhiyun /* Command Execute */
212*4882a593Smuzhiyun map_write(map, exec_cmd, ow_reg_add(map, CMD_EXEC_OFS));
213*4882a593Smuzhiyun if (pcm_data->bus_width == 0x0004) /* 2x16 devices stacked */
214*4882a593Smuzhiyun map_write(map, exec_cmd, ow_reg_add(map, CMD_EXEC_OFS) + 2);
215*4882a593Smuzhiyun
216*4882a593Smuzhiyun /* Status Register Check */
217*4882a593Smuzhiyun do {
218*4882a593Smuzhiyun sr = map_read(map, ow_reg_add(map, STATUS_REG_OFS));
219*4882a593Smuzhiyun status_reg = sr.x[0];
220*4882a593Smuzhiyun if (pcm_data->bus_width == 0x0004) {/* 2x16 devices stacked */
221*4882a593Smuzhiyun sr = map_read(map, ow_reg_add(map,
222*4882a593Smuzhiyun STATUS_REG_OFS) + 2);
223*4882a593Smuzhiyun status_reg += sr.x[0] << 16;
224*4882a593Smuzhiyun }
225*4882a593Smuzhiyun } while ((status_reg & sr_ok_datamask) != sr_ok_datamask);
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun return (((status_reg & sr_ok_datamask) == sr_ok_datamask) ? 0 : -EIO);
228*4882a593Smuzhiyun }
229*4882a593Smuzhiyun
230*4882a593Smuzhiyun /*
231*4882a593Smuzhiyun * Execute lpddr2-nvm operations @ block level
232*4882a593Smuzhiyun */
lpddr2_nvm_do_block_op(struct mtd_info * mtd,loff_t start_add,uint64_t len,u_char block_op)233*4882a593Smuzhiyun static int lpddr2_nvm_do_block_op(struct mtd_info *mtd, loff_t start_add,
234*4882a593Smuzhiyun uint64_t len, u_char block_op)
235*4882a593Smuzhiyun {
236*4882a593Smuzhiyun struct map_info *map = mtd->priv;
237*4882a593Smuzhiyun u_long add, end_add;
238*4882a593Smuzhiyun int ret = 0;
239*4882a593Smuzhiyun
240*4882a593Smuzhiyun mutex_lock(&lpdd2_nvm_mutex);
241*4882a593Smuzhiyun
242*4882a593Smuzhiyun ow_enable(map);
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun add = start_add;
245*4882a593Smuzhiyun end_add = add + len;
246*4882a593Smuzhiyun
247*4882a593Smuzhiyun do {
248*4882a593Smuzhiyun ret = lpddr2_nvm_do_op(map, block_op, 0x00, add, add, NULL);
249*4882a593Smuzhiyun if (ret)
250*4882a593Smuzhiyun goto out;
251*4882a593Smuzhiyun add += mtd->erasesize;
252*4882a593Smuzhiyun } while (add < end_add);
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun out:
255*4882a593Smuzhiyun ow_disable(map);
256*4882a593Smuzhiyun mutex_unlock(&lpdd2_nvm_mutex);
257*4882a593Smuzhiyun return ret;
258*4882a593Smuzhiyun }
259*4882a593Smuzhiyun
260*4882a593Smuzhiyun /*
261*4882a593Smuzhiyun * verify presence of PFOW string
262*4882a593Smuzhiyun */
lpddr2_nvm_pfow_present(struct map_info * map)263*4882a593Smuzhiyun static int lpddr2_nvm_pfow_present(struct map_info *map)
264*4882a593Smuzhiyun {
265*4882a593Smuzhiyun map_word pfow_val[4];
266*4882a593Smuzhiyun unsigned int found = 1;
267*4882a593Smuzhiyun
268*4882a593Smuzhiyun mutex_lock(&lpdd2_nvm_mutex);
269*4882a593Smuzhiyun
270*4882a593Smuzhiyun ow_enable(map);
271*4882a593Smuzhiyun
272*4882a593Smuzhiyun /* Load string from array */
273*4882a593Smuzhiyun pfow_val[0] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_P));
274*4882a593Smuzhiyun pfow_val[1] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_F));
275*4882a593Smuzhiyun pfow_val[2] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_O));
276*4882a593Smuzhiyun pfow_val[3] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_W));
277*4882a593Smuzhiyun
278*4882a593Smuzhiyun /* Verify the string loaded vs expected */
279*4882a593Smuzhiyun if (!map_word_equal(map, build_map_word('P'), pfow_val[0]))
280*4882a593Smuzhiyun found = 0;
281*4882a593Smuzhiyun if (!map_word_equal(map, build_map_word('F'), pfow_val[1]))
282*4882a593Smuzhiyun found = 0;
283*4882a593Smuzhiyun if (!map_word_equal(map, build_map_word('O'), pfow_val[2]))
284*4882a593Smuzhiyun found = 0;
285*4882a593Smuzhiyun if (!map_word_equal(map, build_map_word('W'), pfow_val[3]))
286*4882a593Smuzhiyun found = 0;
287*4882a593Smuzhiyun
288*4882a593Smuzhiyun ow_disable(map);
289*4882a593Smuzhiyun
290*4882a593Smuzhiyun mutex_unlock(&lpdd2_nvm_mutex);
291*4882a593Smuzhiyun
292*4882a593Smuzhiyun return found;
293*4882a593Smuzhiyun }
294*4882a593Smuzhiyun
295*4882a593Smuzhiyun /*
296*4882a593Smuzhiyun * lpddr2_nvm driver read method
297*4882a593Smuzhiyun */
lpddr2_nvm_read(struct mtd_info * mtd,loff_t start_add,size_t len,size_t * retlen,u_char * buf)298*4882a593Smuzhiyun static int lpddr2_nvm_read(struct mtd_info *mtd, loff_t start_add,
299*4882a593Smuzhiyun size_t len, size_t *retlen, u_char *buf)
300*4882a593Smuzhiyun {
301*4882a593Smuzhiyun struct map_info *map = mtd->priv;
302*4882a593Smuzhiyun
303*4882a593Smuzhiyun mutex_lock(&lpdd2_nvm_mutex);
304*4882a593Smuzhiyun
305*4882a593Smuzhiyun *retlen = len;
306*4882a593Smuzhiyun
307*4882a593Smuzhiyun map_copy_from(map, buf, start_add, *retlen);
308*4882a593Smuzhiyun
309*4882a593Smuzhiyun mutex_unlock(&lpdd2_nvm_mutex);
310*4882a593Smuzhiyun return 0;
311*4882a593Smuzhiyun }
312*4882a593Smuzhiyun
313*4882a593Smuzhiyun /*
314*4882a593Smuzhiyun * lpddr2_nvm driver write method
315*4882a593Smuzhiyun */
lpddr2_nvm_write(struct mtd_info * mtd,loff_t start_add,size_t len,size_t * retlen,const u_char * buf)316*4882a593Smuzhiyun static int lpddr2_nvm_write(struct mtd_info *mtd, loff_t start_add,
317*4882a593Smuzhiyun size_t len, size_t *retlen, const u_char *buf)
318*4882a593Smuzhiyun {
319*4882a593Smuzhiyun struct map_info *map = mtd->priv;
320*4882a593Smuzhiyun struct pcm_int_data *pcm_data = map->fldrv_priv;
321*4882a593Smuzhiyun u_long add, current_len, tot_len, target_len, my_data;
322*4882a593Smuzhiyun u_char *write_buf = (u_char *)buf;
323*4882a593Smuzhiyun int ret = 0;
324*4882a593Smuzhiyun
325*4882a593Smuzhiyun mutex_lock(&lpdd2_nvm_mutex);
326*4882a593Smuzhiyun
327*4882a593Smuzhiyun ow_enable(map);
328*4882a593Smuzhiyun
329*4882a593Smuzhiyun /* Set start value for the variables */
330*4882a593Smuzhiyun add = start_add;
331*4882a593Smuzhiyun target_len = len;
332*4882a593Smuzhiyun tot_len = 0;
333*4882a593Smuzhiyun
334*4882a593Smuzhiyun while (tot_len < target_len) {
335*4882a593Smuzhiyun if (!(IS_ALIGNED(add, mtd->writesize))) { /* do sw program */
336*4882a593Smuzhiyun my_data = write_buf[tot_len];
337*4882a593Smuzhiyun my_data += (write_buf[tot_len+1]) << 8;
338*4882a593Smuzhiyun if (pcm_data->bus_width == 0x0004) {/* 2x16 devices */
339*4882a593Smuzhiyun my_data += (write_buf[tot_len+2]) << 16;
340*4882a593Smuzhiyun my_data += (write_buf[tot_len+3]) << 24;
341*4882a593Smuzhiyun }
342*4882a593Smuzhiyun ret = lpddr2_nvm_do_op(map, LPDDR2_NVM_SW_OVERWRITE,
343*4882a593Smuzhiyun my_data, add, 0x00, NULL);
344*4882a593Smuzhiyun if (ret)
345*4882a593Smuzhiyun goto out;
346*4882a593Smuzhiyun
347*4882a593Smuzhiyun add += pcm_data->bus_width;
348*4882a593Smuzhiyun tot_len += pcm_data->bus_width;
349*4882a593Smuzhiyun } else { /* do buffer program */
350*4882a593Smuzhiyun current_len = min(target_len - tot_len,
351*4882a593Smuzhiyun (u_long) mtd->writesize);
352*4882a593Smuzhiyun ret = lpddr2_nvm_do_op(map, LPDDR2_NVM_BUF_OVERWRITE,
353*4882a593Smuzhiyun 0x00, add, current_len, write_buf + tot_len);
354*4882a593Smuzhiyun if (ret)
355*4882a593Smuzhiyun goto out;
356*4882a593Smuzhiyun
357*4882a593Smuzhiyun add += current_len;
358*4882a593Smuzhiyun tot_len += current_len;
359*4882a593Smuzhiyun }
360*4882a593Smuzhiyun }
361*4882a593Smuzhiyun
362*4882a593Smuzhiyun out:
363*4882a593Smuzhiyun *retlen = tot_len;
364*4882a593Smuzhiyun ow_disable(map);
365*4882a593Smuzhiyun mutex_unlock(&lpdd2_nvm_mutex);
366*4882a593Smuzhiyun return ret;
367*4882a593Smuzhiyun }
368*4882a593Smuzhiyun
369*4882a593Smuzhiyun /*
370*4882a593Smuzhiyun * lpddr2_nvm driver erase method
371*4882a593Smuzhiyun */
lpddr2_nvm_erase(struct mtd_info * mtd,struct erase_info * instr)372*4882a593Smuzhiyun static int lpddr2_nvm_erase(struct mtd_info *mtd, struct erase_info *instr)
373*4882a593Smuzhiyun {
374*4882a593Smuzhiyun return lpddr2_nvm_do_block_op(mtd, instr->addr, instr->len,
375*4882a593Smuzhiyun LPDDR2_NVM_ERASE);
376*4882a593Smuzhiyun }
377*4882a593Smuzhiyun
378*4882a593Smuzhiyun /*
379*4882a593Smuzhiyun * lpddr2_nvm driver unlock method
380*4882a593Smuzhiyun */
lpddr2_nvm_unlock(struct mtd_info * mtd,loff_t start_add,uint64_t len)381*4882a593Smuzhiyun static int lpddr2_nvm_unlock(struct mtd_info *mtd, loff_t start_add,
382*4882a593Smuzhiyun uint64_t len)
383*4882a593Smuzhiyun {
384*4882a593Smuzhiyun return lpddr2_nvm_do_block_op(mtd, start_add, len, LPDDR2_NVM_UNLOCK);
385*4882a593Smuzhiyun }
386*4882a593Smuzhiyun
387*4882a593Smuzhiyun /*
388*4882a593Smuzhiyun * lpddr2_nvm driver lock method
389*4882a593Smuzhiyun */
lpddr2_nvm_lock(struct mtd_info * mtd,loff_t start_add,uint64_t len)390*4882a593Smuzhiyun static int lpddr2_nvm_lock(struct mtd_info *mtd, loff_t start_add,
391*4882a593Smuzhiyun uint64_t len)
392*4882a593Smuzhiyun {
393*4882a593Smuzhiyun return lpddr2_nvm_do_block_op(mtd, start_add, len, LPDDR2_NVM_LOCK);
394*4882a593Smuzhiyun }
395*4882a593Smuzhiyun
396*4882a593Smuzhiyun static const struct mtd_info lpddr2_nvm_mtd_info = {
397*4882a593Smuzhiyun .type = MTD_RAM,
398*4882a593Smuzhiyun .writesize = 1,
399*4882a593Smuzhiyun .flags = (MTD_CAP_NVRAM | MTD_POWERUP_LOCK),
400*4882a593Smuzhiyun ._read = lpddr2_nvm_read,
401*4882a593Smuzhiyun ._write = lpddr2_nvm_write,
402*4882a593Smuzhiyun ._erase = lpddr2_nvm_erase,
403*4882a593Smuzhiyun ._unlock = lpddr2_nvm_unlock,
404*4882a593Smuzhiyun ._lock = lpddr2_nvm_lock,
405*4882a593Smuzhiyun };
406*4882a593Smuzhiyun
407*4882a593Smuzhiyun /*
408*4882a593Smuzhiyun * lpddr2_nvm driver probe method
409*4882a593Smuzhiyun */
lpddr2_nvm_probe(struct platform_device * pdev)410*4882a593Smuzhiyun static int lpddr2_nvm_probe(struct platform_device *pdev)
411*4882a593Smuzhiyun {
412*4882a593Smuzhiyun struct map_info *map;
413*4882a593Smuzhiyun struct mtd_info *mtd;
414*4882a593Smuzhiyun struct resource *add_range;
415*4882a593Smuzhiyun struct resource *control_regs;
416*4882a593Smuzhiyun struct pcm_int_data *pcm_data;
417*4882a593Smuzhiyun
418*4882a593Smuzhiyun /* Allocate memory control_regs data structures */
419*4882a593Smuzhiyun pcm_data = devm_kzalloc(&pdev->dev, sizeof(*pcm_data), GFP_KERNEL);
420*4882a593Smuzhiyun if (!pcm_data)
421*4882a593Smuzhiyun return -ENOMEM;
422*4882a593Smuzhiyun
423*4882a593Smuzhiyun pcm_data->bus_width = BUS_WIDTH;
424*4882a593Smuzhiyun
425*4882a593Smuzhiyun /* Allocate memory for map_info & mtd_info data structures */
426*4882a593Smuzhiyun map = devm_kzalloc(&pdev->dev, sizeof(*map), GFP_KERNEL);
427*4882a593Smuzhiyun if (!map)
428*4882a593Smuzhiyun return -ENOMEM;
429*4882a593Smuzhiyun
430*4882a593Smuzhiyun mtd = devm_kzalloc(&pdev->dev, sizeof(*mtd), GFP_KERNEL);
431*4882a593Smuzhiyun if (!mtd)
432*4882a593Smuzhiyun return -ENOMEM;
433*4882a593Smuzhiyun
434*4882a593Smuzhiyun /* lpddr2_nvm address range */
435*4882a593Smuzhiyun add_range = platform_get_resource(pdev, IORESOURCE_MEM, 0);
436*4882a593Smuzhiyun
437*4882a593Smuzhiyun /* Populate map_info data structure */
438*4882a593Smuzhiyun *map = (struct map_info) {
439*4882a593Smuzhiyun .virt = devm_ioremap_resource(&pdev->dev, add_range),
440*4882a593Smuzhiyun .name = pdev->dev.init_name,
441*4882a593Smuzhiyun .phys = add_range->start,
442*4882a593Smuzhiyun .size = resource_size(add_range),
443*4882a593Smuzhiyun .bankwidth = pcm_data->bus_width / 2,
444*4882a593Smuzhiyun .pfow_base = OW_BASE_ADDRESS,
445*4882a593Smuzhiyun .fldrv_priv = pcm_data,
446*4882a593Smuzhiyun };
447*4882a593Smuzhiyun
448*4882a593Smuzhiyun if (IS_ERR(map->virt))
449*4882a593Smuzhiyun return PTR_ERR(map->virt);
450*4882a593Smuzhiyun
451*4882a593Smuzhiyun simple_map_init(map); /* fill with default methods */
452*4882a593Smuzhiyun
453*4882a593Smuzhiyun control_regs = platform_get_resource(pdev, IORESOURCE_MEM, 1);
454*4882a593Smuzhiyun pcm_data->ctl_regs = devm_ioremap_resource(&pdev->dev, control_regs);
455*4882a593Smuzhiyun if (IS_ERR(pcm_data->ctl_regs))
456*4882a593Smuzhiyun return PTR_ERR(pcm_data->ctl_regs);
457*4882a593Smuzhiyun
458*4882a593Smuzhiyun /* Populate mtd_info data structure */
459*4882a593Smuzhiyun *mtd = lpddr2_nvm_mtd_info;
460*4882a593Smuzhiyun mtd->dev.parent = &pdev->dev;
461*4882a593Smuzhiyun mtd->name = pdev->dev.init_name;
462*4882a593Smuzhiyun mtd->priv = map;
463*4882a593Smuzhiyun mtd->size = resource_size(add_range);
464*4882a593Smuzhiyun mtd->erasesize = ERASE_BLOCKSIZE * pcm_data->bus_width;
465*4882a593Smuzhiyun mtd->writebufsize = WRITE_BUFFSIZE * pcm_data->bus_width;
466*4882a593Smuzhiyun
467*4882a593Smuzhiyun /* Verify the presence of the device looking for PFOW string */
468*4882a593Smuzhiyun if (!lpddr2_nvm_pfow_present(map)) {
469*4882a593Smuzhiyun pr_err("device not recognized\n");
470*4882a593Smuzhiyun return -EINVAL;
471*4882a593Smuzhiyun }
472*4882a593Smuzhiyun /* Parse partitions and register the MTD device */
473*4882a593Smuzhiyun return mtd_device_register(mtd, NULL, 0);
474*4882a593Smuzhiyun }
475*4882a593Smuzhiyun
476*4882a593Smuzhiyun /*
477*4882a593Smuzhiyun * lpddr2_nvm driver remove method
478*4882a593Smuzhiyun */
lpddr2_nvm_remove(struct platform_device * pdev)479*4882a593Smuzhiyun static int lpddr2_nvm_remove(struct platform_device *pdev)
480*4882a593Smuzhiyun {
481*4882a593Smuzhiyun return mtd_device_unregister(dev_get_drvdata(&pdev->dev));
482*4882a593Smuzhiyun }
483*4882a593Smuzhiyun
484*4882a593Smuzhiyun /* Initialize platform_driver data structure for lpddr2_nvm */
485*4882a593Smuzhiyun static struct platform_driver lpddr2_nvm_drv = {
486*4882a593Smuzhiyun .driver = {
487*4882a593Smuzhiyun .name = "lpddr2_nvm",
488*4882a593Smuzhiyun },
489*4882a593Smuzhiyun .probe = lpddr2_nvm_probe,
490*4882a593Smuzhiyun .remove = lpddr2_nvm_remove,
491*4882a593Smuzhiyun };
492*4882a593Smuzhiyun
493*4882a593Smuzhiyun module_platform_driver(lpddr2_nvm_drv);
494*4882a593Smuzhiyun MODULE_LICENSE("GPL");
495*4882a593Smuzhiyun MODULE_AUTHOR("Vincenzo Aliberti <vincenzo.aliberti@gmail.com>");
496*4882a593Smuzhiyun MODULE_DESCRIPTION("MTD driver for LPDDR2-NVM PCM memories");
497