brintos

brintos / linux-shallow public Read only

0
0
Text · 6.8 KiB · 137ae5f Raw
236 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Probing flash chips with QINFO records.4 * (C) 2008 Korolev Alexey <akorolev@infradead.org>5 * (C) 2008 Vasiliy Leonenko <vasiliy.leonenko@gmail.com>6 */7#include <linux/module.h>8#include <linux/types.h>9#include <linux/kernel.h>10#include <linux/init.h>11#include <linux/errno.h>12#include <linux/slab.h>13#include <linux/interrupt.h>14 15#include <linux/mtd/xip.h>16#include <linux/mtd/map.h>17#include <linux/mtd/pfow.h>18#include <linux/mtd/qinfo.h>19 20static int lpddr_chip_setup(struct map_info *map, struct lpddr_private *lpddr);21struct mtd_info *lpddr_probe(struct map_info *map);22static struct lpddr_private *lpddr_probe_chip(struct map_info *map);23static int lpddr_pfow_present(struct map_info *map,24			struct lpddr_private *lpddr);25 26static struct qinfo_query_info qinfo_array[] = {27	/* General device info */28	{0, 0, "DevSizeShift", "Device size 2^n bytes"},29	{0, 3, "BufSizeShift", "Program buffer size 2^n bytes"},30	/* Erase block information */31	{1, 1, "TotalBlocksNum", "Total number of blocks"},32	{1, 2, "UniformBlockSizeShift", "Uniform block size 2^n bytes"},33	/* Partition information */34	{2, 1, "HWPartsNum", "Number of hardware partitions"},35	/* Optional features */36	{5, 1, "SuspEraseSupp", "Suspend erase supported"},37	/* Operation typical time */38	{10, 0, "SingleWordProgTime", "Single word program 2^n u-sec"},39	{10, 1, "ProgBufferTime", "Program buffer write 2^n u-sec"},40	{10, 2, "BlockEraseTime", "Block erase 2^n m-sec"},41	{10, 3, "FullChipEraseTime", "Full chip erase 2^n m-sec"},42};43 44static long lpddr_get_qinforec_pos(struct map_info *map, char *id_str)45{46	int qinfo_lines = ARRAY_SIZE(qinfo_array);47	int i;48	int bankwidth = map_bankwidth(map) * 8;49	int major, minor;50 51	for (i = 0; i < qinfo_lines; i++) {52		if (strcmp(id_str, qinfo_array[i].id_str) == 0) {53			major = qinfo_array[i].major & ((1 << bankwidth) - 1);54			minor = qinfo_array[i].minor & ((1 << bankwidth) - 1);55			return minor | (major << bankwidth);56		}57	}58	printk(KERN_ERR"%s qinfo id string is wrong! \n", map->name);59	BUG();60	return -1;61}62 63static uint16_t lpddr_info_query(struct map_info *map, char *id_str)64{65	unsigned int dsr, val;66	int bits_per_chip = map_bankwidth(map) * 8;67	unsigned long adr = lpddr_get_qinforec_pos(map, id_str);68	int attempts = 20;69 70	/* Write a request for the PFOW record */71	map_write(map, CMD(LPDDR_INFO_QUERY),72			map->pfow_base + PFOW_COMMAND_CODE);73	map_write(map, CMD(adr & ((1 << bits_per_chip) - 1)),74			map->pfow_base + PFOW_COMMAND_ADDRESS_L);75	map_write(map, CMD(adr >> bits_per_chip),76			map->pfow_base + PFOW_COMMAND_ADDRESS_H);77	map_write(map, CMD(LPDDR_START_EXECUTION),78			map->pfow_base + PFOW_COMMAND_EXECUTE);79 80	while ((attempts--) > 0) {81		dsr = CMDVAL(map_read(map, map->pfow_base + PFOW_DSR));82		if (dsr & DSR_READY_STATUS)83			break;84		udelay(10);85	}86 87	val = CMDVAL(map_read(map, map->pfow_base + PFOW_COMMAND_DATA));88	return val;89}90 91static int lpddr_pfow_present(struct map_info *map, struct lpddr_private *lpddr)92{93	map_word pfow_val[4];94 95	/* Check identification string */96	pfow_val[0] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_P);97	pfow_val[1] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_F);98	pfow_val[2] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_O);99	pfow_val[3] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_W);100 101	if (!map_word_equal(map, CMD('P'), pfow_val[0]))102		goto out;103 104	if (!map_word_equal(map, CMD('F'), pfow_val[1]))105		goto out;106 107	if (!map_word_equal(map, CMD('O'), pfow_val[2]))108		goto out;109 110	if (!map_word_equal(map, CMD('W'), pfow_val[3]))111		goto out;112 113	return 1;	/* "PFOW" is found */114out:115	printk(KERN_WARNING"%s: PFOW string at 0x%lx is not found \n",116					map->name, map->pfow_base);117	return 0;118}119 120static int lpddr_chip_setup(struct map_info *map, struct lpddr_private *lpddr)121{122 123	lpddr->qinfo = kzalloc(sizeof(struct qinfo_chip), GFP_KERNEL);124	if (!lpddr->qinfo)125		return 0;126 127	/* Get the ManuID */128	lpddr->ManufactId = CMDVAL(map_read(map, map->pfow_base + PFOW_MANUFACTURER_ID));129	/* Get the DeviceID */130	lpddr->DevId = CMDVAL(map_read(map, map->pfow_base + PFOW_DEVICE_ID));131	/* read parameters from chip qinfo table */132	lpddr->qinfo->DevSizeShift = lpddr_info_query(map, "DevSizeShift");133	lpddr->qinfo->TotalBlocksNum = lpddr_info_query(map, "TotalBlocksNum");134	lpddr->qinfo->BufSizeShift = lpddr_info_query(map, "BufSizeShift");135	lpddr->qinfo->HWPartsNum = lpddr_info_query(map, "HWPartsNum");136	lpddr->qinfo->UniformBlockSizeShift =137				lpddr_info_query(map, "UniformBlockSizeShift");138	lpddr->qinfo->SuspEraseSupp = lpddr_info_query(map, "SuspEraseSupp");139	lpddr->qinfo->SingleWordProgTime =140				lpddr_info_query(map, "SingleWordProgTime");141	lpddr->qinfo->ProgBufferTime = lpddr_info_query(map, "ProgBufferTime");142	lpddr->qinfo->BlockEraseTime = lpddr_info_query(map, "BlockEraseTime");143	return 1;144}145static struct lpddr_private *lpddr_probe_chip(struct map_info *map)146{147	struct lpddr_private lpddr;148	struct lpddr_private *retlpddr;149	int numvirtchips;150 151 152	if ((map->pfow_base + 0x1000) >= map->size) {153		printk(KERN_NOTICE"%s Probe at base (0x%08lx) past the end of"154				"the map(0x%08lx)\n", map->name,155				(unsigned long)map->pfow_base, map->size - 1);156		return NULL;157	}158	memset(&lpddr, 0, sizeof(struct lpddr_private));159	if (!lpddr_pfow_present(map, &lpddr))160		return NULL;161 162	if (!lpddr_chip_setup(map, &lpddr))163		return NULL;164 165	/* Ok so we found a chip */166	lpddr.chipshift = lpddr.qinfo->DevSizeShift;167	lpddr.numchips = 1;168 169	numvirtchips = lpddr.numchips * lpddr.qinfo->HWPartsNum;170	retlpddr = kzalloc(struct_size(retlpddr, chips, numvirtchips),171			   GFP_KERNEL);172	if (!retlpddr)173		return NULL;174 175	memcpy(retlpddr, &lpddr, sizeof(struct lpddr_private));176 177	retlpddr->numchips = numvirtchips;178	retlpddr->chipshift = retlpddr->qinfo->DevSizeShift -179				__ffs(retlpddr->qinfo->HWPartsNum);180 181	return retlpddr;182}183 184struct mtd_info *lpddr_probe(struct map_info *map)185{186	struct mtd_info *mtd = NULL;187	struct lpddr_private *lpddr;188 189	/* First probe the map to see if we havecan open PFOW here */190	lpddr = lpddr_probe_chip(map);191	if (!lpddr)192		return NULL;193 194	map->fldrv_priv = lpddr;195	mtd = lpddr_cmdset(map);196	if (mtd) {197		if (mtd->size > map->size) {198			printk(KERN_WARNING "Reducing visibility of %ldKiB chip"199				"to %ldKiB\n", (unsigned long)mtd->size >> 10,200				(unsigned long)map->size >> 10);201			mtd->size = map->size;202		}203		return mtd;204	}205 206	kfree(lpddr->qinfo);207	kfree(lpddr);208	map->fldrv_priv = NULL;209	return NULL;210}211 212static struct mtd_chip_driver lpddr_chipdrv = {213	.probe		= lpddr_probe,214	.name		= "qinfo_probe",215	.module		= THIS_MODULE216};217 218static int __init lpddr_probe_init(void)219{220	register_mtd_chip_driver(&lpddr_chipdrv);221	return 0;222}223 224static void __exit lpddr_probe_exit(void)225{226	unregister_mtd_chip_driver(&lpddr_chipdrv);227}228 229module_init(lpddr_probe_init);230module_exit(lpddr_probe_exit);231 232MODULE_LICENSE("GPL");233MODULE_AUTHOR("Vasiliy Leonenko <vasiliy.leonenko@gmail.com>");234MODULE_DESCRIPTION("Driver to probe qinfo flash chips");235 236