mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
29
drivers/mtd/lpddr/Kconfig
Normal file
29
drivers/mtd/lpddr/Kconfig
Normal file
|
@ -0,0 +1,29 @@
|
|||
menu "LPDDR & LPDDR2 PCM memory drivers"
|
||||
depends on MTD
|
||||
|
||||
config MTD_LPDDR
|
||||
tristate "Support for LPDDR flash chips"
|
||||
select MTD_QINFO_PROBE
|
||||
help
|
||||
This option enables support of LPDDR (Low power double data rate)
|
||||
flash chips. Synonymous with Mobile-DDR. It is a new standard for
|
||||
DDR memories, intended for battery-operated systems.
|
||||
|
||||
config MTD_QINFO_PROBE
|
||||
depends on MTD_LPDDR
|
||||
tristate "Detect flash chips by QINFO probe"
|
||||
help
|
||||
Device Information for LPDDR chips is offered through the Overlay
|
||||
Window QINFO interface, permits software to be used for entire
|
||||
families of devices. This serves similar purpose of CFI on legacy
|
||||
Flash products
|
||||
|
||||
config MTD_LPDDR2_NVM
|
||||
# ARM dependency is only for writel_relaxed()
|
||||
depends on MTD && ARM
|
||||
tristate "Support for LPDDR2-NVM flash chips"
|
||||
help
|
||||
This option enables support of PCM memories with a LPDDR2-NVM
|
||||
(Low power double data rate 2) interface.
|
||||
|
||||
endmenu
|
7
drivers/mtd/lpddr/Makefile
Normal file
7
drivers/mtd/lpddr/Makefile
Normal file
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# linux/drivers/mtd/lpddr/Makefile
|
||||
#
|
||||
|
||||
obj-$(CONFIG_MTD_QINFO_PROBE) += qinfo_probe.o
|
||||
obj-$(CONFIG_MTD_LPDDR) += lpddr_cmds.o
|
||||
obj-$(CONFIG_MTD_LPDDR2_NVM) += lpddr2_nvm.o
|
507
drivers/mtd/lpddr/lpddr2_nvm.c
Normal file
507
drivers/mtd/lpddr/lpddr2_nvm.c
Normal file
|
@ -0,0 +1,507 @@
|
|||
/*
|
||||
* LPDDR2-NVM MTD driver. This module provides read, write, erase, lock/unlock
|
||||
* support for LPDDR2-NVM PCM memories
|
||||
*
|
||||
* Copyright © 2012 Micron Technology, Inc.
|
||||
*
|
||||
* Vincenzo Aliberti <vincenzo.aliberti@gmail.com>
|
||||
* Domenico Manna <domenico.manna@gmail.com>
|
||||
* Many thanks to Andrea Vigilante for initial enabling
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
/* Parameters */
|
||||
#define ERASE_BLOCKSIZE (0x00020000/2) /* in Word */
|
||||
#define WRITE_BUFFSIZE (0x00000400/2) /* in Word */
|
||||
#define OW_BASE_ADDRESS 0x00000000 /* OW offset */
|
||||
#define BUS_WIDTH 0x00000020 /* x32 devices */
|
||||
|
||||
/* PFOW symbols address offset */
|
||||
#define PFOW_QUERY_STRING_P (0x0000/2) /* in Word */
|
||||
#define PFOW_QUERY_STRING_F (0x0002/2) /* in Word */
|
||||
#define PFOW_QUERY_STRING_O (0x0004/2) /* in Word */
|
||||
#define PFOW_QUERY_STRING_W (0x0006/2) /* in Word */
|
||||
|
||||
/* OW registers address */
|
||||
#define CMD_CODE_OFS (0x0080/2) /* in Word */
|
||||
#define CMD_DATA_OFS (0x0084/2) /* in Word */
|
||||
#define CMD_ADD_L_OFS (0x0088/2) /* in Word */
|
||||
#define CMD_ADD_H_OFS (0x008A/2) /* in Word */
|
||||
#define MPR_L_OFS (0x0090/2) /* in Word */
|
||||
#define MPR_H_OFS (0x0092/2) /* in Word */
|
||||
#define CMD_EXEC_OFS (0x00C0/2) /* in Word */
|
||||
#define STATUS_REG_OFS (0x00CC/2) /* in Word */
|
||||
#define PRG_BUFFER_OFS (0x0010/2) /* in Word */
|
||||
|
||||
/* Datamask */
|
||||
#define MR_CFGMASK 0x8000
|
||||
#define SR_OK_DATAMASK 0x0080
|
||||
|
||||
/* LPDDR2-NVM Commands */
|
||||
#define LPDDR2_NVM_LOCK 0x0061
|
||||
#define LPDDR2_NVM_UNLOCK 0x0062
|
||||
#define LPDDR2_NVM_SW_PROGRAM 0x0041
|
||||
#define LPDDR2_NVM_SW_OVERWRITE 0x0042
|
||||
#define LPDDR2_NVM_BUF_PROGRAM 0x00E9
|
||||
#define LPDDR2_NVM_BUF_OVERWRITE 0x00EA
|
||||
#define LPDDR2_NVM_ERASE 0x0020
|
||||
|
||||
/* LPDDR2-NVM Registers offset */
|
||||
#define LPDDR2_MODE_REG_DATA 0x0040
|
||||
#define LPDDR2_MODE_REG_CFG 0x0050
|
||||
|
||||
/*
|
||||
* Internal Type Definitions
|
||||
* pcm_int_data contains memory controller details:
|
||||
* @reg_data : LPDDR2_MODE_REG_DATA register address after remapping
|
||||
* @reg_cfg : LPDDR2_MODE_REG_CFG register address after remapping
|
||||
* &bus_width: memory bus-width (eg: x16 2 Bytes, x32 4 Bytes)
|
||||
*/
|
||||
struct pcm_int_data {
|
||||
void __iomem *ctl_regs;
|
||||
int bus_width;
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(lpdd2_nvm_mutex);
|
||||
|
||||
/*
|
||||
* Build a map_word starting from an u_long
|
||||
*/
|
||||
static inline map_word build_map_word(u_long myword)
|
||||
{
|
||||
map_word val = { {0} };
|
||||
val.x[0] = myword;
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build Mode Register Configuration DataMask based on device bus-width
|
||||
*/
|
||||
static inline u_int build_mr_cfgmask(u_int bus_width)
|
||||
{
|
||||
u_int val = MR_CFGMASK;
|
||||
|
||||
if (bus_width == 0x0004) /* x32 device */
|
||||
val = val << 16;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build Status Register OK DataMask based on device bus-width
|
||||
*/
|
||||
static inline u_int build_sr_ok_datamask(u_int bus_width)
|
||||
{
|
||||
u_int val = SR_OK_DATAMASK;
|
||||
|
||||
if (bus_width == 0x0004) /* x32 device */
|
||||
val = (val << 16)+val;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Evaluates Overlay Window Control Registers address
|
||||
*/
|
||||
static inline u_long ow_reg_add(struct map_info *map, u_long offset)
|
||||
{
|
||||
u_long val = 0;
|
||||
struct pcm_int_data *pcm_data = map->fldrv_priv;
|
||||
|
||||
val = map->pfow_base + offset*pcm_data->bus_width;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable lpddr2-nvm Overlay Window
|
||||
* Overlay Window is a memory mapped area containing all LPDDR2-NVM registers
|
||||
* used by device commands as well as uservisible resources like Device Status
|
||||
* Register, Device ID, etc
|
||||
*/
|
||||
static inline void ow_enable(struct map_info *map)
|
||||
{
|
||||
struct pcm_int_data *pcm_data = map->fldrv_priv;
|
||||
|
||||
writel_relaxed(build_mr_cfgmask(pcm_data->bus_width) | 0x18,
|
||||
pcm_data->ctl_regs + LPDDR2_MODE_REG_CFG);
|
||||
writel_relaxed(0x01, pcm_data->ctl_regs + LPDDR2_MODE_REG_DATA);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable lpddr2-nvm Overlay Window
|
||||
* Overlay Window is a memory mapped area containing all LPDDR2-NVM registers
|
||||
* used by device commands as well as uservisible resources like Device Status
|
||||
* Register, Device ID, etc
|
||||
*/
|
||||
static inline void ow_disable(struct map_info *map)
|
||||
{
|
||||
struct pcm_int_data *pcm_data = map->fldrv_priv;
|
||||
|
||||
writel_relaxed(build_mr_cfgmask(pcm_data->bus_width) | 0x18,
|
||||
pcm_data->ctl_regs + LPDDR2_MODE_REG_CFG);
|
||||
writel_relaxed(0x02, pcm_data->ctl_regs + LPDDR2_MODE_REG_DATA);
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute lpddr2-nvm operations
|
||||
*/
|
||||
static int 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)
|
||||
{
|
||||
map_word add_l = { {0} }, add_h = { {0} }, mpr_l = { {0} },
|
||||
mpr_h = { {0} }, data_l = { {0} }, cmd = { {0} },
|
||||
exec_cmd = { {0} }, sr;
|
||||
map_word data_h = { {0} }; /* only for 2x x16 devices stacked */
|
||||
u_long i, status_reg, prg_buff_ofs;
|
||||
struct pcm_int_data *pcm_data = map->fldrv_priv;
|
||||
u_int sr_ok_datamask = build_sr_ok_datamask(pcm_data->bus_width);
|
||||
|
||||
/* Builds low and high words for OW Control Registers */
|
||||
add_l.x[0] = cmd_add & 0x0000FFFF;
|
||||
add_h.x[0] = (cmd_add >> 16) & 0x0000FFFF;
|
||||
mpr_l.x[0] = cmd_mpr & 0x0000FFFF;
|
||||
mpr_h.x[0] = (cmd_mpr >> 16) & 0x0000FFFF;
|
||||
cmd.x[0] = cmd_code & 0x0000FFFF;
|
||||
exec_cmd.x[0] = 0x0001;
|
||||
data_l.x[0] = cmd_data & 0x0000FFFF;
|
||||
data_h.x[0] = (cmd_data >> 16) & 0x0000FFFF; /* only for 2x x16 */
|
||||
|
||||
/* Set Overlay Window Control Registers */
|
||||
map_write(map, cmd, ow_reg_add(map, CMD_CODE_OFS));
|
||||
map_write(map, data_l, ow_reg_add(map, CMD_DATA_OFS));
|
||||
map_write(map, add_l, ow_reg_add(map, CMD_ADD_L_OFS));
|
||||
map_write(map, add_h, ow_reg_add(map, CMD_ADD_H_OFS));
|
||||
map_write(map, mpr_l, ow_reg_add(map, MPR_L_OFS));
|
||||
map_write(map, mpr_h, ow_reg_add(map, MPR_H_OFS));
|
||||
if (pcm_data->bus_width == 0x0004) { /* 2x16 devices stacked */
|
||||
map_write(map, cmd, ow_reg_add(map, CMD_CODE_OFS) + 2);
|
||||
map_write(map, data_h, ow_reg_add(map, CMD_DATA_OFS) + 2);
|
||||
map_write(map, add_l, ow_reg_add(map, CMD_ADD_L_OFS) + 2);
|
||||
map_write(map, add_h, ow_reg_add(map, CMD_ADD_H_OFS) + 2);
|
||||
map_write(map, mpr_l, ow_reg_add(map, MPR_L_OFS) + 2);
|
||||
map_write(map, mpr_h, ow_reg_add(map, MPR_H_OFS) + 2);
|
||||
}
|
||||
|
||||
/* Fill Program Buffer */
|
||||
if ((cmd_code == LPDDR2_NVM_BUF_PROGRAM) ||
|
||||
(cmd_code == LPDDR2_NVM_BUF_OVERWRITE)) {
|
||||
prg_buff_ofs = (map_read(map,
|
||||
ow_reg_add(map, PRG_BUFFER_OFS))).x[0];
|
||||
for (i = 0; i < cmd_mpr; i++) {
|
||||
map_write(map, build_map_word(buf[i]), map->pfow_base +
|
||||
prg_buff_ofs + i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Command Execute */
|
||||
map_write(map, exec_cmd, ow_reg_add(map, CMD_EXEC_OFS));
|
||||
if (pcm_data->bus_width == 0x0004) /* 2x16 devices stacked */
|
||||
map_write(map, exec_cmd, ow_reg_add(map, CMD_EXEC_OFS) + 2);
|
||||
|
||||
/* Status Register Check */
|
||||
do {
|
||||
sr = map_read(map, ow_reg_add(map, STATUS_REG_OFS));
|
||||
status_reg = sr.x[0];
|
||||
if (pcm_data->bus_width == 0x0004) {/* 2x16 devices stacked */
|
||||
sr = map_read(map, ow_reg_add(map,
|
||||
STATUS_REG_OFS) + 2);
|
||||
status_reg += sr.x[0] << 16;
|
||||
}
|
||||
} while ((status_reg & sr_ok_datamask) != sr_ok_datamask);
|
||||
|
||||
return (((status_reg & sr_ok_datamask) == sr_ok_datamask) ? 0 : -EIO);
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute lpddr2-nvm operations @ block level
|
||||
*/
|
||||
static int lpddr2_nvm_do_block_op(struct mtd_info *mtd, loff_t start_add,
|
||||
uint64_t len, u_char block_op)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
u_long add, end_add;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&lpdd2_nvm_mutex);
|
||||
|
||||
ow_enable(map);
|
||||
|
||||
add = start_add;
|
||||
end_add = add + len;
|
||||
|
||||
do {
|
||||
ret = lpddr2_nvm_do_op(map, block_op, 0x00, add, add, NULL);
|
||||
if (ret)
|
||||
goto out;
|
||||
add += mtd->erasesize;
|
||||
} while (add < end_add);
|
||||
|
||||
out:
|
||||
ow_disable(map);
|
||||
mutex_unlock(&lpdd2_nvm_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* verify presence of PFOW string
|
||||
*/
|
||||
static int lpddr2_nvm_pfow_present(struct map_info *map)
|
||||
{
|
||||
map_word pfow_val[4];
|
||||
unsigned int found = 1;
|
||||
|
||||
mutex_lock(&lpdd2_nvm_mutex);
|
||||
|
||||
ow_enable(map);
|
||||
|
||||
/* Load string from array */
|
||||
pfow_val[0] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_P));
|
||||
pfow_val[1] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_F));
|
||||
pfow_val[2] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_O));
|
||||
pfow_val[3] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_W));
|
||||
|
||||
/* Verify the string loaded vs expected */
|
||||
if (!map_word_equal(map, build_map_word('P'), pfow_val[0]))
|
||||
found = 0;
|
||||
if (!map_word_equal(map, build_map_word('F'), pfow_val[1]))
|
||||
found = 0;
|
||||
if (!map_word_equal(map, build_map_word('O'), pfow_val[2]))
|
||||
found = 0;
|
||||
if (!map_word_equal(map, build_map_word('W'), pfow_val[3]))
|
||||
found = 0;
|
||||
|
||||
ow_disable(map);
|
||||
|
||||
mutex_unlock(&lpdd2_nvm_mutex);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/*
|
||||
* lpddr2_nvm driver read method
|
||||
*/
|
||||
static int lpddr2_nvm_read(struct mtd_info *mtd, loff_t start_add,
|
||||
size_t len, size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
|
||||
mutex_lock(&lpdd2_nvm_mutex);
|
||||
|
||||
*retlen = len;
|
||||
|
||||
map_copy_from(map, buf, start_add, *retlen);
|
||||
|
||||
mutex_unlock(&lpdd2_nvm_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* lpddr2_nvm driver write method
|
||||
*/
|
||||
static int lpddr2_nvm_write(struct mtd_info *mtd, loff_t start_add,
|
||||
size_t len, size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct pcm_int_data *pcm_data = map->fldrv_priv;
|
||||
u_long add, current_len, tot_len, target_len, my_data;
|
||||
u_char *write_buf = (u_char *)buf;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&lpdd2_nvm_mutex);
|
||||
|
||||
ow_enable(map);
|
||||
|
||||
/* Set start value for the variables */
|
||||
add = start_add;
|
||||
target_len = len;
|
||||
tot_len = 0;
|
||||
|
||||
while (tot_len < target_len) {
|
||||
if (!(IS_ALIGNED(add, mtd->writesize))) { /* do sw program */
|
||||
my_data = write_buf[tot_len];
|
||||
my_data += (write_buf[tot_len+1]) << 8;
|
||||
if (pcm_data->bus_width == 0x0004) {/* 2x16 devices */
|
||||
my_data += (write_buf[tot_len+2]) << 16;
|
||||
my_data += (write_buf[tot_len+3]) << 24;
|
||||
}
|
||||
ret = lpddr2_nvm_do_op(map, LPDDR2_NVM_SW_OVERWRITE,
|
||||
my_data, add, 0x00, NULL);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
add += pcm_data->bus_width;
|
||||
tot_len += pcm_data->bus_width;
|
||||
} else { /* do buffer program */
|
||||
current_len = min(target_len - tot_len,
|
||||
(u_long) mtd->writesize);
|
||||
ret = lpddr2_nvm_do_op(map, LPDDR2_NVM_BUF_OVERWRITE,
|
||||
0x00, add, current_len, write_buf + tot_len);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
add += current_len;
|
||||
tot_len += current_len;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
*retlen = tot_len;
|
||||
ow_disable(map);
|
||||
mutex_unlock(&lpdd2_nvm_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* lpddr2_nvm driver erase method
|
||||
*/
|
||||
static int lpddr2_nvm_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
int ret = lpddr2_nvm_do_block_op(mtd, instr->addr, instr->len,
|
||||
LPDDR2_NVM_ERASE);
|
||||
if (!ret) {
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
mtd_erase_callback(instr);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* lpddr2_nvm driver unlock method
|
||||
*/
|
||||
static int lpddr2_nvm_unlock(struct mtd_info *mtd, loff_t start_add,
|
||||
uint64_t len)
|
||||
{
|
||||
return lpddr2_nvm_do_block_op(mtd, start_add, len, LPDDR2_NVM_UNLOCK);
|
||||
}
|
||||
|
||||
/*
|
||||
* lpddr2_nvm driver lock method
|
||||
*/
|
||||
static int lpddr2_nvm_lock(struct mtd_info *mtd, loff_t start_add,
|
||||
uint64_t len)
|
||||
{
|
||||
return lpddr2_nvm_do_block_op(mtd, start_add, len, LPDDR2_NVM_LOCK);
|
||||
}
|
||||
|
||||
/*
|
||||
* lpddr2_nvm driver probe method
|
||||
*/
|
||||
static int lpddr2_nvm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct map_info *map;
|
||||
struct mtd_info *mtd;
|
||||
struct resource *add_range;
|
||||
struct resource *control_regs;
|
||||
struct pcm_int_data *pcm_data;
|
||||
|
||||
/* Allocate memory control_regs data structures */
|
||||
pcm_data = devm_kzalloc(&pdev->dev, sizeof(*pcm_data), GFP_KERNEL);
|
||||
if (!pcm_data)
|
||||
return -ENOMEM;
|
||||
|
||||
pcm_data->bus_width = BUS_WIDTH;
|
||||
|
||||
/* Allocate memory for map_info & mtd_info data structures */
|
||||
map = devm_kzalloc(&pdev->dev, sizeof(*map), GFP_KERNEL);
|
||||
if (!map)
|
||||
return -ENOMEM;
|
||||
|
||||
mtd = devm_kzalloc(&pdev->dev, sizeof(*mtd), GFP_KERNEL);
|
||||
if (!mtd)
|
||||
return -ENOMEM;
|
||||
|
||||
/* lpddr2_nvm address range */
|
||||
add_range = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
/* Populate map_info data structure */
|
||||
*map = (struct map_info) {
|
||||
.virt = devm_ioremap_resource(&pdev->dev, add_range),
|
||||
.name = pdev->dev.init_name,
|
||||
.phys = add_range->start,
|
||||
.size = resource_size(add_range),
|
||||
.bankwidth = pcm_data->bus_width / 2,
|
||||
.pfow_base = OW_BASE_ADDRESS,
|
||||
.fldrv_priv = pcm_data,
|
||||
};
|
||||
if (IS_ERR(map->virt))
|
||||
return PTR_ERR(map->virt);
|
||||
|
||||
simple_map_init(map); /* fill with default methods */
|
||||
|
||||
control_regs = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
pcm_data->ctl_regs = devm_ioremap_resource(&pdev->dev, control_regs);
|
||||
if (IS_ERR(pcm_data->ctl_regs))
|
||||
return PTR_ERR(pcm_data->ctl_regs);
|
||||
|
||||
/* Populate mtd_info data structure */
|
||||
*mtd = (struct mtd_info) {
|
||||
.name = pdev->dev.init_name,
|
||||
.type = MTD_RAM,
|
||||
.priv = map,
|
||||
.size = resource_size(add_range),
|
||||
.erasesize = ERASE_BLOCKSIZE * pcm_data->bus_width,
|
||||
.writesize = 1,
|
||||
.writebufsize = WRITE_BUFFSIZE * pcm_data->bus_width,
|
||||
.flags = (MTD_CAP_NVRAM | MTD_POWERUP_LOCK),
|
||||
._read = lpddr2_nvm_read,
|
||||
._write = lpddr2_nvm_write,
|
||||
._erase = lpddr2_nvm_erase,
|
||||
._unlock = lpddr2_nvm_unlock,
|
||||
._lock = lpddr2_nvm_lock,
|
||||
};
|
||||
|
||||
/* Verify the presence of the device looking for PFOW string */
|
||||
if (!lpddr2_nvm_pfow_present(map)) {
|
||||
pr_err("device not recognized\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Parse partitions and register the MTD device */
|
||||
return mtd_device_parse_register(mtd, NULL, NULL, NULL, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* lpddr2_nvm driver remove method
|
||||
*/
|
||||
static int lpddr2_nvm_remove(struct platform_device *pdev)
|
||||
{
|
||||
return mtd_device_unregister(dev_get_drvdata(&pdev->dev));
|
||||
}
|
||||
|
||||
/* Initialize platform_driver data structure for lpddr2_nvm */
|
||||
static struct platform_driver lpddr2_nvm_drv = {
|
||||
.driver = {
|
||||
.name = "lpddr2_nvm",
|
||||
},
|
||||
.probe = lpddr2_nvm_probe,
|
||||
.remove = lpddr2_nvm_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(lpddr2_nvm_drv);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Vincenzo Aliberti <vincenzo.aliberti@gmail.com>");
|
||||
MODULE_DESCRIPTION("MTD driver for LPDDR2-NVM PCM memories");
|
751
drivers/mtd/lpddr/lpddr_cmds.c
Normal file
751
drivers/mtd/lpddr/lpddr_cmds.c
Normal file
|
@ -0,0 +1,751 @@
|
|||
/*
|
||||
* LPDDR flash memory device operations. This module provides read, write,
|
||||
* erase, lock/unlock support for LPDDR flash memories
|
||||
* (C) 2008 Korolev Alexey <akorolev@infradead.org>
|
||||
* (C) 2008 Vasiliy Leonenko <vasiliy.leonenko@gmail.com>
|
||||
* Many thanks to Roman Borisov for initial enabling
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
* TODO:
|
||||
* Implement VPP management
|
||||
* Implement XIP support
|
||||
* Implement OTP support
|
||||
*/
|
||||
#include <linux/mtd/pfow.h>
|
||||
#include <linux/mtd/qinfo.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
static int lpddr_read(struct mtd_info *mtd, loff_t adr, size_t len,
|
||||
size_t *retlen, u_char *buf);
|
||||
static int lpddr_write_buffers(struct mtd_info *mtd, loff_t to,
|
||||
size_t len, size_t *retlen, const u_char *buf);
|
||||
static int lpddr_writev(struct mtd_info *mtd, const struct kvec *vecs,
|
||||
unsigned long count, loff_t to, size_t *retlen);
|
||||
static int lpddr_erase(struct mtd_info *mtd, struct erase_info *instr);
|
||||
static int lpddr_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
static int lpddr_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
static int lpddr_point(struct mtd_info *mtd, loff_t adr, size_t len,
|
||||
size_t *retlen, void **mtdbuf, resource_size_t *phys);
|
||||
static int lpddr_unpoint(struct mtd_info *mtd, loff_t adr, size_t len);
|
||||
static int get_chip(struct map_info *map, struct flchip *chip, int mode);
|
||||
static int chip_ready(struct map_info *map, struct flchip *chip, int mode);
|
||||
static void put_chip(struct map_info *map, struct flchip *chip);
|
||||
|
||||
struct mtd_info *lpddr_cmdset(struct map_info *map)
|
||||
{
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
struct flchip_shared *shared;
|
||||
struct flchip *chip;
|
||||
struct mtd_info *mtd;
|
||||
int numchips;
|
||||
int i, j;
|
||||
|
||||
mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
|
||||
if (!mtd)
|
||||
return NULL;
|
||||
mtd->priv = map;
|
||||
mtd->type = MTD_NORFLASH;
|
||||
|
||||
/* Fill in the default mtd operations */
|
||||
mtd->_read = lpddr_read;
|
||||
mtd->type = MTD_NORFLASH;
|
||||
mtd->flags = MTD_CAP_NORFLASH;
|
||||
mtd->flags &= ~MTD_BIT_WRITEABLE;
|
||||
mtd->_erase = lpddr_erase;
|
||||
mtd->_write = lpddr_write_buffers;
|
||||
mtd->_writev = lpddr_writev;
|
||||
mtd->_lock = lpddr_lock;
|
||||
mtd->_unlock = lpddr_unlock;
|
||||
if (map_is_linear(map)) {
|
||||
mtd->_point = lpddr_point;
|
||||
mtd->_unpoint = lpddr_unpoint;
|
||||
}
|
||||
mtd->size = 1 << lpddr->qinfo->DevSizeShift;
|
||||
mtd->erasesize = 1 << lpddr->qinfo->UniformBlockSizeShift;
|
||||
mtd->writesize = 1 << lpddr->qinfo->BufSizeShift;
|
||||
|
||||
shared = kmalloc(sizeof(struct flchip_shared) * lpddr->numchips,
|
||||
GFP_KERNEL);
|
||||
if (!shared) {
|
||||
kfree(lpddr);
|
||||
kfree(mtd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
chip = &lpddr->chips[0];
|
||||
numchips = lpddr->numchips / lpddr->qinfo->HWPartsNum;
|
||||
for (i = 0; i < numchips; i++) {
|
||||
shared[i].writing = shared[i].erasing = NULL;
|
||||
mutex_init(&shared[i].lock);
|
||||
for (j = 0; j < lpddr->qinfo->HWPartsNum; j++) {
|
||||
*chip = lpddr->chips[i];
|
||||
chip->start += j << lpddr->chipshift;
|
||||
chip->oldstate = chip->state = FL_READY;
|
||||
chip->priv = &shared[i];
|
||||
/* those should be reset too since
|
||||
they create memory references. */
|
||||
init_waitqueue_head(&chip->wq);
|
||||
mutex_init(&chip->mutex);
|
||||
chip++;
|
||||
}
|
||||
}
|
||||
|
||||
return mtd;
|
||||
}
|
||||
EXPORT_SYMBOL(lpddr_cmdset);
|
||||
|
||||
static int wait_for_ready(struct map_info *map, struct flchip *chip,
|
||||
unsigned int chip_op_time)
|
||||
{
|
||||
unsigned int timeo, reset_timeo, sleep_time;
|
||||
unsigned int dsr;
|
||||
flstate_t chip_state = chip->state;
|
||||
int ret = 0;
|
||||
|
||||
/* set our timeout to 8 times the expected delay */
|
||||
timeo = chip_op_time * 8;
|
||||
if (!timeo)
|
||||
timeo = 500000;
|
||||
reset_timeo = timeo;
|
||||
sleep_time = chip_op_time / 2;
|
||||
|
||||
for (;;) {
|
||||
dsr = CMDVAL(map_read(map, map->pfow_base + PFOW_DSR));
|
||||
if (dsr & DSR_READY_STATUS)
|
||||
break;
|
||||
if (!timeo) {
|
||||
printk(KERN_ERR "%s: Flash timeout error state %d \n",
|
||||
map->name, chip_state);
|
||||
ret = -ETIME;
|
||||
break;
|
||||
}
|
||||
|
||||
/* OK Still waiting. Drop the lock, wait a while and retry. */
|
||||
mutex_unlock(&chip->mutex);
|
||||
if (sleep_time >= 1000000/HZ) {
|
||||
/*
|
||||
* Half of the normal delay still remaining
|
||||
* can be performed with a sleeping delay instead
|
||||
* of busy waiting.
|
||||
*/
|
||||
msleep(sleep_time/1000);
|
||||
timeo -= sleep_time;
|
||||
sleep_time = 1000000/HZ;
|
||||
} else {
|
||||
udelay(1);
|
||||
cond_resched();
|
||||
timeo--;
|
||||
}
|
||||
mutex_lock(&chip->mutex);
|
||||
|
||||
while (chip->state != chip_state) {
|
||||
/* Someone's suspended the operation: sleep */
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
add_wait_queue(&chip->wq, &wait);
|
||||
mutex_unlock(&chip->mutex);
|
||||
schedule();
|
||||
remove_wait_queue(&chip->wq, &wait);
|
||||
mutex_lock(&chip->mutex);
|
||||
}
|
||||
if (chip->erase_suspended || chip->write_suspended) {
|
||||
/* Suspend has occurred while sleep: reset timeout */
|
||||
timeo = reset_timeo;
|
||||
chip->erase_suspended = chip->write_suspended = 0;
|
||||
}
|
||||
}
|
||||
/* check status for errors */
|
||||
if (dsr & DSR_ERR) {
|
||||
/* Clear DSR*/
|
||||
map_write(map, CMD(~(DSR_ERR)), map->pfow_base + PFOW_DSR);
|
||||
printk(KERN_WARNING"%s: Bad status on wait: 0x%x \n",
|
||||
map->name, dsr);
|
||||
print_drs_error(dsr);
|
||||
ret = -EIO;
|
||||
}
|
||||
chip->state = FL_READY;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_chip(struct map_info *map, struct flchip *chip, int mode)
|
||||
{
|
||||
int ret;
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
|
||||
retry:
|
||||
if (chip->priv && (mode == FL_WRITING || mode == FL_ERASING)
|
||||
&& chip->state != FL_SYNCING) {
|
||||
/*
|
||||
* OK. We have possibility for contension on the write/erase
|
||||
* operations which are global to the real chip and not per
|
||||
* partition. So let's fight it over in the partition which
|
||||
* currently has authority on the operation.
|
||||
*
|
||||
* The rules are as follows:
|
||||
*
|
||||
* - any write operation must own shared->writing.
|
||||
*
|
||||
* - any erase operation must own _both_ shared->writing and
|
||||
* shared->erasing.
|
||||
*
|
||||
* - contension arbitration is handled in the owner's context.
|
||||
*
|
||||
* The 'shared' struct can be read and/or written only when
|
||||
* its lock is taken.
|
||||
*/
|
||||
struct flchip_shared *shared = chip->priv;
|
||||
struct flchip *contender;
|
||||
mutex_lock(&shared->lock);
|
||||
contender = shared->writing;
|
||||
if (contender && contender != chip) {
|
||||
/*
|
||||
* The engine to perform desired operation on this
|
||||
* partition is already in use by someone else.
|
||||
* Let's fight over it in the context of the chip
|
||||
* currently using it. If it is possible to suspend,
|
||||
* that other partition will do just that, otherwise
|
||||
* it'll happily send us to sleep. In any case, when
|
||||
* get_chip returns success we're clear to go ahead.
|
||||
*/
|
||||
ret = mutex_trylock(&contender->mutex);
|
||||
mutex_unlock(&shared->lock);
|
||||
if (!ret)
|
||||
goto retry;
|
||||
mutex_unlock(&chip->mutex);
|
||||
ret = chip_ready(map, contender, mode);
|
||||
mutex_lock(&chip->mutex);
|
||||
|
||||
if (ret == -EAGAIN) {
|
||||
mutex_unlock(&contender->mutex);
|
||||
goto retry;
|
||||
}
|
||||
if (ret) {
|
||||
mutex_unlock(&contender->mutex);
|
||||
return ret;
|
||||
}
|
||||
mutex_lock(&shared->lock);
|
||||
|
||||
/* We should not own chip if it is already in FL_SYNCING
|
||||
* state. Put contender and retry. */
|
||||
if (chip->state == FL_SYNCING) {
|
||||
put_chip(map, contender);
|
||||
mutex_unlock(&contender->mutex);
|
||||
goto retry;
|
||||
}
|
||||
mutex_unlock(&contender->mutex);
|
||||
}
|
||||
|
||||
/* Check if we have suspended erase on this chip.
|
||||
Must sleep in such a case. */
|
||||
if (mode == FL_ERASING && shared->erasing
|
||||
&& shared->erasing->oldstate == FL_ERASING) {
|
||||
mutex_unlock(&shared->lock);
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
add_wait_queue(&chip->wq, &wait);
|
||||
mutex_unlock(&chip->mutex);
|
||||
schedule();
|
||||
remove_wait_queue(&chip->wq, &wait);
|
||||
mutex_lock(&chip->mutex);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
/* We now own it */
|
||||
shared->writing = chip;
|
||||
if (mode == FL_ERASING)
|
||||
shared->erasing = chip;
|
||||
mutex_unlock(&shared->lock);
|
||||
}
|
||||
|
||||
ret = chip_ready(map, chip, mode);
|
||||
if (ret == -EAGAIN)
|
||||
goto retry;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int chip_ready(struct map_info *map, struct flchip *chip, int mode)
|
||||
{
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int ret = 0;
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
|
||||
/* Prevent setting state FL_SYNCING for chip in suspended state. */
|
||||
if (FL_SYNCING == mode && FL_READY != chip->oldstate)
|
||||
goto sleep;
|
||||
|
||||
switch (chip->state) {
|
||||
case FL_READY:
|
||||
case FL_JEDEC_QUERY:
|
||||
return 0;
|
||||
|
||||
case FL_ERASING:
|
||||
if (!lpddr->qinfo->SuspEraseSupp ||
|
||||
!(mode == FL_READY || mode == FL_POINT))
|
||||
goto sleep;
|
||||
|
||||
map_write(map, CMD(LPDDR_SUSPEND),
|
||||
map->pfow_base + PFOW_PROGRAM_ERASE_SUSPEND);
|
||||
chip->oldstate = FL_ERASING;
|
||||
chip->state = FL_ERASE_SUSPENDING;
|
||||
ret = wait_for_ready(map, chip, 0);
|
||||
if (ret) {
|
||||
/* Oops. something got wrong. */
|
||||
/* Resume and pretend we weren't here. */
|
||||
put_chip(map, chip);
|
||||
printk(KERN_ERR "%s: suspend operation failed."
|
||||
"State may be wrong \n", map->name);
|
||||
return -EIO;
|
||||
}
|
||||
chip->erase_suspended = 1;
|
||||
chip->state = FL_READY;
|
||||
return 0;
|
||||
/* Erase suspend */
|
||||
case FL_POINT:
|
||||
/* Only if there's no operation suspended... */
|
||||
if (mode == FL_READY && chip->oldstate == FL_READY)
|
||||
return 0;
|
||||
|
||||
default:
|
||||
sleep:
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
add_wait_queue(&chip->wq, &wait);
|
||||
mutex_unlock(&chip->mutex);
|
||||
schedule();
|
||||
remove_wait_queue(&chip->wq, &wait);
|
||||
mutex_lock(&chip->mutex);
|
||||
return -EAGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
static void put_chip(struct map_info *map, struct flchip *chip)
|
||||
{
|
||||
if (chip->priv) {
|
||||
struct flchip_shared *shared = chip->priv;
|
||||
mutex_lock(&shared->lock);
|
||||
if (shared->writing == chip && chip->oldstate == FL_READY) {
|
||||
/* We own the ability to write, but we're done */
|
||||
shared->writing = shared->erasing;
|
||||
if (shared->writing && shared->writing != chip) {
|
||||
/* give back the ownership */
|
||||
struct flchip *loaner = shared->writing;
|
||||
mutex_lock(&loaner->mutex);
|
||||
mutex_unlock(&shared->lock);
|
||||
mutex_unlock(&chip->mutex);
|
||||
put_chip(map, loaner);
|
||||
mutex_lock(&chip->mutex);
|
||||
mutex_unlock(&loaner->mutex);
|
||||
wake_up(&chip->wq);
|
||||
return;
|
||||
}
|
||||
shared->erasing = NULL;
|
||||
shared->writing = NULL;
|
||||
} else if (shared->erasing == chip && shared->writing != chip) {
|
||||
/*
|
||||
* We own the ability to erase without the ability
|
||||
* to write, which means the erase was suspended
|
||||
* and some other partition is currently writing.
|
||||
* Don't let the switch below mess things up since
|
||||
* we don't have ownership to resume anything.
|
||||
*/
|
||||
mutex_unlock(&shared->lock);
|
||||
wake_up(&chip->wq);
|
||||
return;
|
||||
}
|
||||
mutex_unlock(&shared->lock);
|
||||
}
|
||||
|
||||
switch (chip->oldstate) {
|
||||
case FL_ERASING:
|
||||
map_write(map, CMD(LPDDR_RESUME),
|
||||
map->pfow_base + PFOW_COMMAND_CODE);
|
||||
map_write(map, CMD(LPDDR_START_EXECUTION),
|
||||
map->pfow_base + PFOW_COMMAND_EXECUTE);
|
||||
chip->oldstate = FL_READY;
|
||||
chip->state = FL_ERASING;
|
||||
break;
|
||||
case FL_READY:
|
||||
break;
|
||||
default:
|
||||
printk(KERN_ERR "%s: put_chip() called with oldstate %d!\n",
|
||||
map->name, chip->oldstate);
|
||||
}
|
||||
wake_up(&chip->wq);
|
||||
}
|
||||
|
||||
static int do_write_buffer(struct map_info *map, struct flchip *chip,
|
||||
unsigned long adr, const struct kvec **pvec,
|
||||
unsigned long *pvec_seek, int len)
|
||||
{
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
map_word datum;
|
||||
int ret, wbufsize, word_gap, words;
|
||||
const struct kvec *vec;
|
||||
unsigned long vec_seek;
|
||||
unsigned long prog_buf_ofs;
|
||||
|
||||
wbufsize = 1 << lpddr->qinfo->BufSizeShift;
|
||||
|
||||
mutex_lock(&chip->mutex);
|
||||
ret = get_chip(map, chip, FL_WRITING);
|
||||
if (ret) {
|
||||
mutex_unlock(&chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
/* Figure out the number of words to write */
|
||||
word_gap = (-adr & (map_bankwidth(map)-1));
|
||||
words = (len - word_gap + map_bankwidth(map) - 1) / map_bankwidth(map);
|
||||
if (!word_gap) {
|
||||
words--;
|
||||
} else {
|
||||
word_gap = map_bankwidth(map) - word_gap;
|
||||
adr -= word_gap;
|
||||
datum = map_word_ff(map);
|
||||
}
|
||||
/* Write data */
|
||||
/* Get the program buffer offset from PFOW register data first*/
|
||||
prog_buf_ofs = map->pfow_base + CMDVAL(map_read(map,
|
||||
map->pfow_base + PFOW_PROGRAM_BUFFER_OFFSET));
|
||||
vec = *pvec;
|
||||
vec_seek = *pvec_seek;
|
||||
do {
|
||||
int n = map_bankwidth(map) - word_gap;
|
||||
|
||||
if (n > vec->iov_len - vec_seek)
|
||||
n = vec->iov_len - vec_seek;
|
||||
if (n > len)
|
||||
n = len;
|
||||
|
||||
if (!word_gap && (len < map_bankwidth(map)))
|
||||
datum = map_word_ff(map);
|
||||
|
||||
datum = map_word_load_partial(map, datum,
|
||||
vec->iov_base + vec_seek, word_gap, n);
|
||||
|
||||
len -= n;
|
||||
word_gap += n;
|
||||
if (!len || word_gap == map_bankwidth(map)) {
|
||||
map_write(map, datum, prog_buf_ofs);
|
||||
prog_buf_ofs += map_bankwidth(map);
|
||||
word_gap = 0;
|
||||
}
|
||||
|
||||
vec_seek += n;
|
||||
if (vec_seek == vec->iov_len) {
|
||||
vec++;
|
||||
vec_seek = 0;
|
||||
}
|
||||
} while (len);
|
||||
*pvec = vec;
|
||||
*pvec_seek = vec_seek;
|
||||
|
||||
/* GO GO GO */
|
||||
send_pfow_command(map, LPDDR_BUFF_PROGRAM, adr, wbufsize, NULL);
|
||||
chip->state = FL_WRITING;
|
||||
ret = wait_for_ready(map, chip, (1<<lpddr->qinfo->ProgBufferTime));
|
||||
if (ret) {
|
||||
printk(KERN_WARNING"%s Buffer program error: %d at %lx; \n",
|
||||
map->name, ret, adr);
|
||||
goto out;
|
||||
}
|
||||
|
||||
out: put_chip(map, chip);
|
||||
mutex_unlock(&chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_erase_oneblock(struct mtd_info *mtd, loff_t adr)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int chipnum = adr >> lpddr->chipshift;
|
||||
struct flchip *chip = &lpddr->chips[chipnum];
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->mutex);
|
||||
ret = get_chip(map, chip, FL_ERASING);
|
||||
if (ret) {
|
||||
mutex_unlock(&chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
send_pfow_command(map, LPDDR_BLOCK_ERASE, adr, 0, NULL);
|
||||
chip->state = FL_ERASING;
|
||||
ret = wait_for_ready(map, chip, (1<<lpddr->qinfo->BlockEraseTime)*1000);
|
||||
if (ret) {
|
||||
printk(KERN_WARNING"%s Erase block error %d at : %llx\n",
|
||||
map->name, ret, adr);
|
||||
goto out;
|
||||
}
|
||||
out: put_chip(map, chip);
|
||||
mutex_unlock(&chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpddr_read(struct mtd_info *mtd, loff_t adr, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int chipnum = adr >> lpddr->chipshift;
|
||||
struct flchip *chip = &lpddr->chips[chipnum];
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&chip->mutex);
|
||||
ret = get_chip(map, chip, FL_READY);
|
||||
if (ret) {
|
||||
mutex_unlock(&chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
map_copy_from(map, buf, adr, len);
|
||||
*retlen = len;
|
||||
|
||||
put_chip(map, chip);
|
||||
mutex_unlock(&chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpddr_point(struct mtd_info *mtd, loff_t adr, size_t len,
|
||||
size_t *retlen, void **mtdbuf, resource_size_t *phys)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int chipnum = adr >> lpddr->chipshift;
|
||||
unsigned long ofs, last_end = 0;
|
||||
struct flchip *chip = &lpddr->chips[chipnum];
|
||||
int ret = 0;
|
||||
|
||||
if (!map->virt)
|
||||
return -EINVAL;
|
||||
|
||||
/* ofs: offset within the first chip that the first read should start */
|
||||
ofs = adr - (chipnum << lpddr->chipshift);
|
||||
*mtdbuf = (void *)map->virt + chip->start + ofs;
|
||||
|
||||
while (len) {
|
||||
unsigned long thislen;
|
||||
|
||||
if (chipnum >= lpddr->numchips)
|
||||
break;
|
||||
|
||||
/* We cannot point across chips that are virtually disjoint */
|
||||
if (!last_end)
|
||||
last_end = chip->start;
|
||||
else if (chip->start != last_end)
|
||||
break;
|
||||
|
||||
if ((len + ofs - 1) >> lpddr->chipshift)
|
||||
thislen = (1<<lpddr->chipshift) - ofs;
|
||||
else
|
||||
thislen = len;
|
||||
/* get the chip */
|
||||
mutex_lock(&chip->mutex);
|
||||
ret = get_chip(map, chip, FL_POINT);
|
||||
mutex_unlock(&chip->mutex);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
chip->state = FL_POINT;
|
||||
chip->ref_point_counter++;
|
||||
*retlen += thislen;
|
||||
len -= thislen;
|
||||
|
||||
ofs = 0;
|
||||
last_end += 1 << lpddr->chipshift;
|
||||
chipnum++;
|
||||
chip = &lpddr->chips[chipnum];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpddr_unpoint (struct mtd_info *mtd, loff_t adr, size_t len)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int chipnum = adr >> lpddr->chipshift, err = 0;
|
||||
unsigned long ofs;
|
||||
|
||||
/* ofs: offset within the first chip that the first read should start */
|
||||
ofs = adr - (chipnum << lpddr->chipshift);
|
||||
|
||||
while (len) {
|
||||
unsigned long thislen;
|
||||
struct flchip *chip;
|
||||
|
||||
chip = &lpddr->chips[chipnum];
|
||||
if (chipnum >= lpddr->numchips)
|
||||
break;
|
||||
|
||||
if ((len + ofs - 1) >> lpddr->chipshift)
|
||||
thislen = (1<<lpddr->chipshift) - ofs;
|
||||
else
|
||||
thislen = len;
|
||||
|
||||
mutex_lock(&chip->mutex);
|
||||
if (chip->state == FL_POINT) {
|
||||
chip->ref_point_counter--;
|
||||
if (chip->ref_point_counter == 0)
|
||||
chip->state = FL_READY;
|
||||
} else {
|
||||
printk(KERN_WARNING "%s: Warning: unpoint called on non"
|
||||
"pointed region\n", map->name);
|
||||
err = -EINVAL;
|
||||
}
|
||||
|
||||
put_chip(map, chip);
|
||||
mutex_unlock(&chip->mutex);
|
||||
|
||||
len -= thislen;
|
||||
ofs = 0;
|
||||
chipnum++;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int lpddr_write_buffers(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct kvec vec;
|
||||
|
||||
vec.iov_base = (void *) buf;
|
||||
vec.iov_len = len;
|
||||
|
||||
return lpddr_writev(mtd, &vec, 1, to, retlen);
|
||||
}
|
||||
|
||||
|
||||
static int lpddr_writev(struct mtd_info *mtd, const struct kvec *vecs,
|
||||
unsigned long count, loff_t to, size_t *retlen)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int ret = 0;
|
||||
int chipnum;
|
||||
unsigned long ofs, vec_seek, i;
|
||||
int wbufsize = 1 << lpddr->qinfo->BufSizeShift;
|
||||
size_t len = 0;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
len += vecs[i].iov_len;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
chipnum = to >> lpddr->chipshift;
|
||||
|
||||
ofs = to;
|
||||
vec_seek = 0;
|
||||
|
||||
do {
|
||||
/* We must not cross write block boundaries */
|
||||
int size = wbufsize - (ofs & (wbufsize-1));
|
||||
|
||||
if (size > len)
|
||||
size = len;
|
||||
|
||||
ret = do_write_buffer(map, &lpddr->chips[chipnum],
|
||||
ofs, &vecs, &vec_seek, size);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ofs += size;
|
||||
(*retlen) += size;
|
||||
len -= size;
|
||||
|
||||
/* Be nice and reschedule with the chip in a usable
|
||||
* state for other processes */
|
||||
cond_resched();
|
||||
|
||||
} while (len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpddr_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
unsigned long ofs, len;
|
||||
int ret;
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int size = 1 << lpddr->qinfo->UniformBlockSizeShift;
|
||||
|
||||
ofs = instr->addr;
|
||||
len = instr->len;
|
||||
|
||||
while (len > 0) {
|
||||
ret = do_erase_oneblock(mtd, ofs);
|
||||
if (ret)
|
||||
return ret;
|
||||
ofs += size;
|
||||
len -= size;
|
||||
}
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
mtd_erase_callback(instr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define DO_XXLOCK_LOCK 1
|
||||
#define DO_XXLOCK_UNLOCK 2
|
||||
static int do_xxlock(struct mtd_info *mtd, loff_t adr, uint32_t len, int thunk)
|
||||
{
|
||||
int ret = 0;
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int chipnum = adr >> lpddr->chipshift;
|
||||
struct flchip *chip = &lpddr->chips[chipnum];
|
||||
|
||||
mutex_lock(&chip->mutex);
|
||||
ret = get_chip(map, chip, FL_LOCKING);
|
||||
if (ret) {
|
||||
mutex_unlock(&chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (thunk == DO_XXLOCK_LOCK) {
|
||||
send_pfow_command(map, LPDDR_LOCK_BLOCK, adr, adr + len, NULL);
|
||||
chip->state = FL_LOCKING;
|
||||
} else if (thunk == DO_XXLOCK_UNLOCK) {
|
||||
send_pfow_command(map, LPDDR_UNLOCK_BLOCK, adr, adr + len, NULL);
|
||||
chip->state = FL_UNLOCKING;
|
||||
} else
|
||||
BUG();
|
||||
|
||||
ret = wait_for_ready(map, chip, 1);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "%s: block unlock error status %d \n",
|
||||
map->name, ret);
|
||||
goto out;
|
||||
}
|
||||
out: put_chip(map, chip);
|
||||
mutex_unlock(&chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpddr_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
return do_xxlock(mtd, ofs, len, DO_XXLOCK_LOCK);
|
||||
}
|
||||
|
||||
static int lpddr_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
return do_xxlock(mtd, ofs, len, DO_XXLOCK_UNLOCK);
|
||||
}
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Alexey Korolev <akorolev@infradead.org>");
|
||||
MODULE_DESCRIPTION("MTD driver for LPDDR flash chips");
|
249
drivers/mtd/lpddr/qinfo_probe.c
Normal file
249
drivers/mtd/lpddr/qinfo_probe.c
Normal file
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* Probing flash chips with QINFO records.
|
||||
* (C) 2008 Korolev Alexey <akorolev@infradead.org>
|
||||
* (C) 2008 Vasiliy Leonenko <vasiliy.leonenko@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
#include <linux/mtd/xip.h>
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/mtd/pfow.h>
|
||||
#include <linux/mtd/qinfo.h>
|
||||
|
||||
static int lpddr_chip_setup(struct map_info *map, struct lpddr_private *lpddr);
|
||||
struct mtd_info *lpddr_probe(struct map_info *map);
|
||||
static struct lpddr_private *lpddr_probe_chip(struct map_info *map);
|
||||
static int lpddr_pfow_present(struct map_info *map,
|
||||
struct lpddr_private *lpddr);
|
||||
|
||||
static struct qinfo_query_info qinfo_array[] = {
|
||||
/* General device info */
|
||||
{0, 0, "DevSizeShift", "Device size 2^n bytes"},
|
||||
{0, 3, "BufSizeShift", "Program buffer size 2^n bytes"},
|
||||
/* Erase block information */
|
||||
{1, 1, "TotalBlocksNum", "Total number of blocks"},
|
||||
{1, 2, "UniformBlockSizeShift", "Uniform block size 2^n bytes"},
|
||||
/* Partition information */
|
||||
{2, 1, "HWPartsNum", "Number of hardware partitions"},
|
||||
/* Optional features */
|
||||
{5, 1, "SuspEraseSupp", "Suspend erase supported"},
|
||||
/* Operation typical time */
|
||||
{10, 0, "SingleWordProgTime", "Single word program 2^n u-sec"},
|
||||
{10, 1, "ProgBufferTime", "Program buffer write 2^n u-sec"},
|
||||
{10, 2, "BlockEraseTime", "Block erase 2^n m-sec"},
|
||||
{10, 3, "FullChipEraseTime", "Full chip erase 2^n m-sec"},
|
||||
};
|
||||
|
||||
static long lpddr_get_qinforec_pos(struct map_info *map, char *id_str)
|
||||
{
|
||||
int qinfo_lines = ARRAY_SIZE(qinfo_array);
|
||||
int i;
|
||||
int bankwidth = map_bankwidth(map) * 8;
|
||||
int major, minor;
|
||||
|
||||
for (i = 0; i < qinfo_lines; i++) {
|
||||
if (strcmp(id_str, qinfo_array[i].id_str) == 0) {
|
||||
major = qinfo_array[i].major & ((1 << bankwidth) - 1);
|
||||
minor = qinfo_array[i].minor & ((1 << bankwidth) - 1);
|
||||
return minor | (major << bankwidth);
|
||||
}
|
||||
}
|
||||
printk(KERN_ERR"%s qinfo id string is wrong! \n", map->name);
|
||||
BUG();
|
||||
return -1;
|
||||
}
|
||||
|
||||
static uint16_t lpddr_info_query(struct map_info *map, char *id_str)
|
||||
{
|
||||
unsigned int dsr, val;
|
||||
int bits_per_chip = map_bankwidth(map) * 8;
|
||||
unsigned long adr = lpddr_get_qinforec_pos(map, id_str);
|
||||
int attempts = 20;
|
||||
|
||||
/* Write a request for the PFOW record */
|
||||
map_write(map, CMD(LPDDR_INFO_QUERY),
|
||||
map->pfow_base + PFOW_COMMAND_CODE);
|
||||
map_write(map, CMD(adr & ((1 << bits_per_chip) - 1)),
|
||||
map->pfow_base + PFOW_COMMAND_ADDRESS_L);
|
||||
map_write(map, CMD(adr >> bits_per_chip),
|
||||
map->pfow_base + PFOW_COMMAND_ADDRESS_H);
|
||||
map_write(map, CMD(LPDDR_START_EXECUTION),
|
||||
map->pfow_base + PFOW_COMMAND_EXECUTE);
|
||||
|
||||
while ((attempts--) > 0) {
|
||||
dsr = CMDVAL(map_read(map, map->pfow_base + PFOW_DSR));
|
||||
if (dsr & DSR_READY_STATUS)
|
||||
break;
|
||||
udelay(10);
|
||||
}
|
||||
|
||||
val = CMDVAL(map_read(map, map->pfow_base + PFOW_COMMAND_DATA));
|
||||
return val;
|
||||
}
|
||||
|
||||
static int lpddr_pfow_present(struct map_info *map, struct lpddr_private *lpddr)
|
||||
{
|
||||
map_word pfow_val[4];
|
||||
|
||||
/* Check identification string */
|
||||
pfow_val[0] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_P);
|
||||
pfow_val[1] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_F);
|
||||
pfow_val[2] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_O);
|
||||
pfow_val[3] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_W);
|
||||
|
||||
if (!map_word_equal(map, CMD('P'), pfow_val[0]))
|
||||
goto out;
|
||||
|
||||
if (!map_word_equal(map, CMD('F'), pfow_val[1]))
|
||||
goto out;
|
||||
|
||||
if (!map_word_equal(map, CMD('O'), pfow_val[2]))
|
||||
goto out;
|
||||
|
||||
if (!map_word_equal(map, CMD('W'), pfow_val[3]))
|
||||
goto out;
|
||||
|
||||
return 1; /* "PFOW" is found */
|
||||
out:
|
||||
printk(KERN_WARNING"%s: PFOW string at 0x%lx is not found \n",
|
||||
map->name, map->pfow_base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpddr_chip_setup(struct map_info *map, struct lpddr_private *lpddr)
|
||||
{
|
||||
|
||||
lpddr->qinfo = kzalloc(sizeof(struct qinfo_chip), GFP_KERNEL);
|
||||
if (!lpddr->qinfo)
|
||||
return 0;
|
||||
|
||||
/* Get the ManuID */
|
||||
lpddr->ManufactId = CMDVAL(map_read(map, map->pfow_base + PFOW_MANUFACTURER_ID));
|
||||
/* Get the DeviceID */
|
||||
lpddr->DevId = CMDVAL(map_read(map, map->pfow_base + PFOW_DEVICE_ID));
|
||||
/* read parameters from chip qinfo table */
|
||||
lpddr->qinfo->DevSizeShift = lpddr_info_query(map, "DevSizeShift");
|
||||
lpddr->qinfo->TotalBlocksNum = lpddr_info_query(map, "TotalBlocksNum");
|
||||
lpddr->qinfo->BufSizeShift = lpddr_info_query(map, "BufSizeShift");
|
||||
lpddr->qinfo->HWPartsNum = lpddr_info_query(map, "HWPartsNum");
|
||||
lpddr->qinfo->UniformBlockSizeShift =
|
||||
lpddr_info_query(map, "UniformBlockSizeShift");
|
||||
lpddr->qinfo->SuspEraseSupp = lpddr_info_query(map, "SuspEraseSupp");
|
||||
lpddr->qinfo->SingleWordProgTime =
|
||||
lpddr_info_query(map, "SingleWordProgTime");
|
||||
lpddr->qinfo->ProgBufferTime = lpddr_info_query(map, "ProgBufferTime");
|
||||
lpddr->qinfo->BlockEraseTime = lpddr_info_query(map, "BlockEraseTime");
|
||||
return 1;
|
||||
}
|
||||
static struct lpddr_private *lpddr_probe_chip(struct map_info *map)
|
||||
{
|
||||
struct lpddr_private lpddr;
|
||||
struct lpddr_private *retlpddr;
|
||||
int numvirtchips;
|
||||
|
||||
|
||||
if ((map->pfow_base + 0x1000) >= map->size) {
|
||||
printk(KERN_NOTICE"%s Probe at base (0x%08lx) past the end of"
|
||||
"the map(0x%08lx)\n", map->name,
|
||||
(unsigned long)map->pfow_base, map->size - 1);
|
||||
return NULL;
|
||||
}
|
||||
memset(&lpddr, 0, sizeof(struct lpddr_private));
|
||||
if (!lpddr_pfow_present(map, &lpddr))
|
||||
return NULL;
|
||||
|
||||
if (!lpddr_chip_setup(map, &lpddr))
|
||||
return NULL;
|
||||
|
||||
/* Ok so we found a chip */
|
||||
lpddr.chipshift = lpddr.qinfo->DevSizeShift;
|
||||
lpddr.numchips = 1;
|
||||
|
||||
numvirtchips = lpddr.numchips * lpddr.qinfo->HWPartsNum;
|
||||
retlpddr = kzalloc(sizeof(struct lpddr_private) +
|
||||
numvirtchips * sizeof(struct flchip), GFP_KERNEL);
|
||||
if (!retlpddr)
|
||||
return NULL;
|
||||
|
||||
memcpy(retlpddr, &lpddr, sizeof(struct lpddr_private));
|
||||
|
||||
retlpddr->numchips = numvirtchips;
|
||||
retlpddr->chipshift = retlpddr->qinfo->DevSizeShift -
|
||||
__ffs(retlpddr->qinfo->HWPartsNum);
|
||||
|
||||
return retlpddr;
|
||||
}
|
||||
|
||||
struct mtd_info *lpddr_probe(struct map_info *map)
|
||||
{
|
||||
struct mtd_info *mtd = NULL;
|
||||
struct lpddr_private *lpddr;
|
||||
|
||||
/* First probe the map to see if we havecan open PFOW here */
|
||||
lpddr = lpddr_probe_chip(map);
|
||||
if (!lpddr)
|
||||
return NULL;
|
||||
|
||||
map->fldrv_priv = lpddr;
|
||||
mtd = lpddr_cmdset(map);
|
||||
if (mtd) {
|
||||
if (mtd->size > map->size) {
|
||||
printk(KERN_WARNING "Reducing visibility of %ldKiB chip"
|
||||
"to %ldKiB\n", (unsigned long)mtd->size >> 10,
|
||||
(unsigned long)map->size >> 10);
|
||||
mtd->size = map->size;
|
||||
}
|
||||
return mtd;
|
||||
}
|
||||
|
||||
kfree(lpddr->qinfo);
|
||||
kfree(lpddr);
|
||||
map->fldrv_priv = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct mtd_chip_driver lpddr_chipdrv = {
|
||||
.probe = lpddr_probe,
|
||||
.name = "qinfo_probe",
|
||||
.module = THIS_MODULE
|
||||
};
|
||||
|
||||
static int __init lpddr_probe_init(void)
|
||||
{
|
||||
register_mtd_chip_driver(&lpddr_chipdrv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit lpddr_probe_exit(void)
|
||||
{
|
||||
unregister_mtd_chip_driver(&lpddr_chipdrv);
|
||||
}
|
||||
|
||||
module_init(lpddr_probe_init);
|
||||
module_exit(lpddr_probe_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Vasiliy Leonenko <vasiliy.leonenko@gmail.com>");
|
||||
MODULE_DESCRIPTION("Driver to probe qinfo flash chips");
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue