blob: b678179bd588ef5499685b6990330786271760b2 [file]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
//#define DEBUG
#include <linux/mmc/core.h>
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/slab.h>
#include <linux/scatterlist.h>
#include <linux/swap.h> /* For nr_free_buffer_pages() */
#include <linux/list.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/seq_file.h>
#include <linux/module.h>
#include "core.h"
#include "card.h"
#include "host.h"
#include "bus.h"
#include "mmc_ops.h"
#include <linux/types.h>
#include <linux/stddef.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/blkdev.h>
#include <linux/scatterlist.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/amlogic/aml_sd.h>
#include "mmc_common.h"
static dev_t amlmmc_dtb_no;
struct cdev amlmmc_dtb;
struct device *dtb_dev;
struct class *amlmmc_dtb_class;
static char *glb_dtb_buf;
struct mmc_card *card_dtb;
struct aml_dtb_info {
unsigned int stamp[2];
u8 valid[2];
};
static struct aml_dtb_info dtb_infos = {{0, 0}, {0, 0} };
struct mmc_partitions_fmt *pt_fmt;
struct mmc_dtb {
struct mmc_card *card; /* the host this device belongs to */
struct device dev; /* the device */
};
#define stamp_after(a, b) ((int)(b) - (int)(a) < 0)
#define CONFIG_DTB_SIZE (256 * 1024U)
#define DTB_CELL_SIZE (16 * 1024U)
#define DTB_NAME "dtb"
#define SZ_1M 0x00100000
#define MMC_DTB_PART_OFFSET (40 * SZ_1M)
#define EMMC_BLOCK_SIZE (0x100)
#define MAX_EMMC_BLOCK_SIZE (128 * 1024)
#define DTB_RESERVE_OFFSET (4 * SZ_1M)
#define DTB_BLK_SIZE (0x200)
#define DTB_BLK_CNT (512)
#define DTB_SIZE (DTB_BLK_CNT * DTB_BLK_SIZE)
#define DTB_COPIES (2)
#define DTB_AREA_BLK_CNT (DTB_BLK_CNT * DTB_COPIES)
/* pertransfer for internal operations. */
#define MAX_TRANS_BLK (256)
#define MAX_TRANS_SIZE (MAX_TRANS_BLK * DTB_BLK_SIZE)
struct aml_dtb_rsv {
u8 data[DTB_BLK_SIZE * DTB_BLK_CNT - 4 * sizeof(unsigned int)];
unsigned int magic;
unsigned int version;
unsigned int timestamp;
unsigned int checksum;
};
static CLASS_ATTR_STRING(emmcdtb, 0644, NULL);
int mmc_dtb_open(struct inode *node, struct file *file)
{
return 0;
}
static int _dtb_write(struct mmc_card *mmc, int blk, unsigned char *buf)
{
int ret = 0;
unsigned char *src = NULL;
int bit = mmc->csd.read_blkbits;
int cnt = CONFIG_DTB_SIZE >> bit;
src = (unsigned char *)buf;
mmc_claim_host(mmc->host);
do {
ret = mmc_write_internal(mmc, blk, MAX_TRANS_BLK, src);
if (ret) {
pr_err("%s: save dtb error", __func__);
ret = -EFAULT;
break;
}
blk += MAX_TRANS_BLK;
cnt -= MAX_TRANS_BLK;
src = (unsigned char *)buf + MAX_TRANS_SIZE;
} while (cnt != 0);
mmc_release_host(mmc->host);
return ret;
}
static unsigned int _calc_dtb_checksum(struct aml_dtb_rsv *dtb)
{
int i = 0;
int size = sizeof(struct aml_dtb_rsv) - sizeof(unsigned int);
unsigned int *buffer;
unsigned int checksum = 0;
size = size >> 2;
buffer = (unsigned int *)dtb;
while (i < size)
checksum += buffer[i++];
return checksum;
}
static int _verify_dtb_checksum(struct aml_dtb_rsv *dtb)
{
unsigned int checksum;
checksum = _calc_dtb_checksum(dtb);
pr_debug("calc %x, store %x\n", checksum, dtb->checksum);
return !(checksum == dtb->checksum);
}
int amlmmc_dtb_write(struct mmc_card *mmc, unsigned char *buf, int len)
{
int ret = 0, blk;
int bit = mmc->csd.read_blkbits;
int cpy, valid;
struct aml_dtb_rsv *dtb = (struct aml_dtb_rsv *)buf;
struct aml_dtb_info *info = &dtb_infos;
if (len > CONFIG_DTB_SIZE) {
pr_err("%s dtb data len too much", __func__);
return -EFAULT;
}
/* set info */
valid = info->valid[0] + info->valid[1];
if (valid == 0) {
dtb->timestamp = 0;
} else if (valid == 1) {
dtb->timestamp = 1 + info->stamp[info->valid[0] ? 0 : 1];
} else {
/* both are valid */
if (info->stamp[0] != info->stamp[1]) {
pr_info("timestamp are not same %d:%d\n",
info->stamp[0], info->stamp[1]);
dtb->timestamp = 1 +
(stamp_after(info->stamp[1], info->stamp[0]) ?
info->stamp[1] : info->stamp[0]);
} else {
dtb->timestamp = 1 + info->stamp[0];
}
}
/*setting version and magic*/
dtb->version = 1; /* base version */
dtb->magic = 0x00447e41; /*A~D\0*/
dtb->checksum = _calc_dtb_checksum(dtb);
pr_info("stamp %d, checksum 0x%x, version %d, magic %s\n",
dtb->timestamp, dtb->checksum,
dtb->version, (char *)&dtb->magic);
/* write down... */
for (cpy = 0; cpy < DTB_COPIES; cpy++) {
blk = ((get_reserve_partition_off_from_tbl()
+ DTB_RESERVE_OFFSET) >> bit)
+ cpy * DTB_BLK_CNT;
ret |= _dtb_write(mmc, blk, buf);
}
return ret;
}
int amlmmc_dtb_read(struct mmc_card *card, unsigned char *buf, int len)
{
int ret = 0, start_blk, size, blk_cnt;
int bit = card->csd.read_blkbits;
unsigned char *dst = NULL;
unsigned char *buffer = NULL;
if (len > CONFIG_DTB_SIZE) {
pr_err("%s dtb data len too much", __func__);
return -EFAULT;
}
memset(buf, 0x0, len);
start_blk = MMC_DTB_PART_OFFSET;
buffer = kmalloc(DTB_CELL_SIZE, GFP_KERNEL | __GFP_RECLAIM);
if (!buffer)
return -ENOMEM;
start_blk >>= bit;
size = CONFIG_DTB_SIZE;
blk_cnt = size >> bit;
dst = (unsigned char *)buffer;
while (blk_cnt != 0) {
memset(buffer, 0x0, DTB_CELL_SIZE);
ret = mmc_read_internal(card, start_blk, (DTB_CELL_SIZE >> bit), dst);
if (ret) {
pr_err("%s read dtb error", __func__);
ret = -EFAULT;
kfree(buffer);
return ret;
}
start_blk += (DTB_CELL_SIZE >> bit);
blk_cnt -= (DTB_CELL_SIZE >> bit);
memcpy(buf, dst, DTB_CELL_SIZE);
buf += DTB_CELL_SIZE;
}
kfree(buffer);
return ret;
}
ssize_t mmc_dtb_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
unsigned char *dtb_ptr = NULL;
ssize_t read_size = 0;
int ret = 0;
if (*ppos == CONFIG_DTB_SIZE)
return 0;
if (*ppos >= CONFIG_DTB_SIZE) {
pr_err("%s: out of space!", __func__);
return -EFAULT;
}
dtb_ptr = glb_dtb_buf;
if (!dtb_ptr)
return -ENOMEM;
mmc_claim_host(card_dtb->host);
aml_disable_mmc_cqe(card_dtb);
ret = amlmmc_dtb_read(card_dtb, (unsigned char *)dtb_ptr, CONFIG_DTB_SIZE);
if (ret) {
pr_err("%s: read failed:%d", __func__, ret);
ret = -EFAULT;
goto exit;
}
if ((*ppos + count) > CONFIG_DTB_SIZE)
read_size = CONFIG_DTB_SIZE - *ppos;
else
read_size = count;
ret = copy_to_user(buf, (dtb_ptr + *ppos), read_size);
*ppos += read_size;
exit:
aml_enable_mmc_cqe(card_dtb);
mmc_release_host(card_dtb->host);
return read_size;
}
ssize_t mmc_dtb_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
unsigned char *dtb_ptr = NULL;
ssize_t write_size = 0;
int ret = 0;
if (*ppos == CONFIG_DTB_SIZE)
return 0;
if (*ppos >= CONFIG_DTB_SIZE) {
pr_err("%s: out of space!", __func__);
return -EFAULT;
}
dtb_ptr = glb_dtb_buf;
if (!dtb_ptr)
return -ENOMEM;
mmc_claim_host(card_dtb->host);
aml_disable_mmc_cqe(card_dtb);
if ((*ppos + count) > CONFIG_DTB_SIZE)
write_size = CONFIG_DTB_SIZE - *ppos;
else
write_size = count;
ret = copy_from_user((dtb_ptr + *ppos), buf, write_size);
ret = amlmmc_dtb_write(card_dtb,
dtb_ptr, CONFIG_DTB_SIZE);
if (ret) {
pr_err("%s: write dtb failed", __func__);
ret = -EFAULT;
goto exit;
}
*ppos += write_size;
exit:
aml_enable_mmc_cqe(card_dtb);
mmc_release_host(card_dtb->host);
return write_size;
}
long mmc_dtb_ioctl(struct file *file, unsigned int cmd, unsigned long args)
{
return 0;
}
static const struct file_operations dtb_ops = {
.open = mmc_dtb_open,
.read = mmc_dtb_read,
.write = mmc_dtb_write,
.unlocked_ioctl = mmc_dtb_ioctl,
};
int get_reserve_partition_off_from_tbl(void)
{
return 0x2400000;
}
static int _dtb_read(struct mmc_card *mmc, int blk, unsigned char *buf)
{
int ret = 0;
unsigned char *dst = NULL;
int bit = mmc->csd.read_blkbits;
int cnt = CONFIG_DTB_SIZE >> bit;
dst = (unsigned char *)buf;
mmc_claim_host(mmc->host);
aml_disable_mmc_cqe(mmc);
do {
ret = mmc_read_internal(mmc, blk, MAX_TRANS_BLK, dst);
if (ret) {
pr_err("%s: save dtb error", __func__);
ret = -EFAULT;
break;
}
blk += MAX_TRANS_BLK;
cnt -= MAX_TRANS_BLK;
dst = (unsigned char *)buf + MAX_TRANS_SIZE;
} while (cnt != 0);
aml_enable_mmc_cqe(mmc);
mmc_release_host(mmc->host);
return ret;
}
static int _dtb_init(struct mmc_card *mmc)
{
int ret = 0;
struct aml_dtb_rsv *dtb;
struct aml_dtb_info *info = &dtb_infos;
int cpy = 1, valid = 0;
int bit = mmc->csd.read_blkbits;
int blk;
if (!glb_dtb_buf) {
glb_dtb_buf = kmalloc(CONFIG_DTB_SIZE, GFP_KERNEL);
if (!glb_dtb_buf)
return -ENOMEM;
}
dtb = (struct aml_dtb_rsv *)glb_dtb_buf;
/* read dtb2 1st, for compatibility without checksum. */
while (cpy >= 0) {
blk = ((get_reserve_partition_off_from_tbl()
+ DTB_RESERVE_OFFSET) >> bit)
+ cpy * DTB_BLK_CNT;
if (_dtb_read(mmc, blk, (unsigned char *)dtb)) {
pr_err("%s: block # %#x ERROR!\n", __func__, blk);
} else {
ret = _verify_dtb_checksum(dtb);
if (!ret) {
info->stamp[cpy] = dtb->timestamp;
info->valid[cpy] = 1;
} else {
pr_debug("cpy %d is not valid\n", cpy);
}
}
valid += info->valid[cpy];
cpy--;
}
pr_debug("total valid %d\n", valid);
return ret;
}
void amlmmc_dtb_init(struct mmc_card *card, int *retp)
{
*retp = 0;
mmc_claim_host(card->host);
card_dtb = card;
pr_debug("%s: register dtb chardev", __func__);
_dtb_init(card);
*retp = alloc_chrdev_region(&amlmmc_dtb_no, 0, 1, DTB_NAME);
if (*retp < 0) {
pr_err("alloc dtb dev_t no failed");
*retp = -1;
goto exit;
}
cdev_init(&amlmmc_dtb, &dtb_ops);
amlmmc_dtb.owner = THIS_MODULE;
*retp = cdev_add(&amlmmc_dtb, amlmmc_dtb_no, 1);
if (*retp) {
pr_err("dtb dev add failed");
*retp = -1;
goto exit_err1;
}
amlmmc_dtb_class = class_create(DTB_NAME);
if (IS_ERR(amlmmc_dtb_class)) {
pr_err("dtb dev add failed");
*retp = -1;
goto exit_err2;
}
*retp = class_create_file(amlmmc_dtb_class, &class_attr_emmcdtb.attr);
if (*retp) {
pr_err("dtb dev add failed");
*retp = -1;
goto exit_err2;
}
dtb_dev = device_create(amlmmc_dtb_class, NULL, amlmmc_dtb_no, NULL, DTB_NAME);
if (IS_ERR(dtb_dev)) {
pr_err("dtb dev add failed");
*retp = -1;
goto exit_err3;
}
pr_info("%s: register dtb chardev OK", __func__);
goto exit;
exit_err3:
class_remove_file(amlmmc_dtb_class, &class_attr_emmcdtb.attr);
class_destroy(amlmmc_dtb_class);
exit_err2:
cdev_del(&amlmmc_dtb);
exit_err1:
unregister_chrdev_region(amlmmc_dtb_no, 1);
exit:
mmc_release_host(card->host);
}