162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci//
362306a36Sopenharmony_ci// Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com/
462306a36Sopenharmony_ci// Author: Vignesh Raghavendra <vigneshr@ti.com>
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/err.h>
762306a36Sopenharmony_ci#include <linux/kernel.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/mtd/hyperbus.h>
1062306a36Sopenharmony_ci#include <linux/mtd/map.h>
1162306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
1262306a36Sopenharmony_ci#include <linux/of.h>
1362306a36Sopenharmony_ci#include <linux/types.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistatic struct hyperbus_device *map_to_hbdev(struct map_info *map)
1662306a36Sopenharmony_ci{
1762306a36Sopenharmony_ci	return container_of(map, struct hyperbus_device, map);
1862306a36Sopenharmony_ci}
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic map_word hyperbus_read16(struct map_info *map, unsigned long addr)
2162306a36Sopenharmony_ci{
2262306a36Sopenharmony_ci	struct hyperbus_device *hbdev = map_to_hbdev(map);
2362306a36Sopenharmony_ci	struct hyperbus_ctlr *ctlr = hbdev->ctlr;
2462306a36Sopenharmony_ci	map_word read_data;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	read_data.x[0] = ctlr->ops->read16(hbdev, addr);
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	return read_data;
2962306a36Sopenharmony_ci}
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic void hyperbus_write16(struct map_info *map, map_word d,
3262306a36Sopenharmony_ci			     unsigned long addr)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	struct hyperbus_device *hbdev = map_to_hbdev(map);
3562306a36Sopenharmony_ci	struct hyperbus_ctlr *ctlr = hbdev->ctlr;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	ctlr->ops->write16(hbdev, addr, d.x[0]);
3862306a36Sopenharmony_ci}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic void hyperbus_copy_from(struct map_info *map, void *to,
4162306a36Sopenharmony_ci			       unsigned long from, ssize_t len)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	struct hyperbus_device *hbdev = map_to_hbdev(map);
4462306a36Sopenharmony_ci	struct hyperbus_ctlr *ctlr = hbdev->ctlr;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	ctlr->ops->copy_from(hbdev, to, from, len);
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic void hyperbus_copy_to(struct map_info *map, unsigned long to,
5062306a36Sopenharmony_ci			     const void *from, ssize_t len)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	struct hyperbus_device *hbdev = map_to_hbdev(map);
5362306a36Sopenharmony_ci	struct hyperbus_ctlr *ctlr = hbdev->ctlr;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	ctlr->ops->copy_to(hbdev, to, from, len);
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ciint hyperbus_register_device(struct hyperbus_device *hbdev)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	const struct hyperbus_ops *ops;
6162306a36Sopenharmony_ci	struct hyperbus_ctlr *ctlr;
6262306a36Sopenharmony_ci	struct device_node *np;
6362306a36Sopenharmony_ci	struct map_info *map;
6462306a36Sopenharmony_ci	struct device *dev;
6562306a36Sopenharmony_ci	int ret;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	if (!hbdev || !hbdev->np || !hbdev->ctlr || !hbdev->ctlr->dev) {
6862306a36Sopenharmony_ci		pr_err("hyperbus: please fill all the necessary fields!\n");
6962306a36Sopenharmony_ci		return -EINVAL;
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	np = hbdev->np;
7362306a36Sopenharmony_ci	ctlr = hbdev->ctlr;
7462306a36Sopenharmony_ci	if (!of_device_is_compatible(np, "cypress,hyperflash")) {
7562306a36Sopenharmony_ci		dev_err(ctlr->dev, "\"cypress,hyperflash\" compatible missing\n");
7662306a36Sopenharmony_ci		return -ENODEV;
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	hbdev->memtype = HYPERFLASH;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	dev = ctlr->dev;
8262306a36Sopenharmony_ci	map = &hbdev->map;
8362306a36Sopenharmony_ci	map->name = dev_name(dev);
8462306a36Sopenharmony_ci	map->bankwidth = 2;
8562306a36Sopenharmony_ci	map->device_node = np;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	simple_map_init(map);
8862306a36Sopenharmony_ci	ops = ctlr->ops;
8962306a36Sopenharmony_ci	if (ops) {
9062306a36Sopenharmony_ci		if (ops->read16)
9162306a36Sopenharmony_ci			map->read = hyperbus_read16;
9262306a36Sopenharmony_ci		if (ops->write16)
9362306a36Sopenharmony_ci			map->write = hyperbus_write16;
9462306a36Sopenharmony_ci		if (ops->copy_to)
9562306a36Sopenharmony_ci			map->copy_to = hyperbus_copy_to;
9662306a36Sopenharmony_ci		if (ops->copy_from)
9762306a36Sopenharmony_ci			map->copy_from = hyperbus_copy_from;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci		if (ops->calibrate && !ctlr->calibrated) {
10062306a36Sopenharmony_ci			ret = ops->calibrate(hbdev);
10162306a36Sopenharmony_ci			if (!ret) {
10262306a36Sopenharmony_ci				dev_err(dev, "Calibration failed\n");
10362306a36Sopenharmony_ci				return -ENODEV;
10462306a36Sopenharmony_ci			}
10562306a36Sopenharmony_ci			ctlr->calibrated = true;
10662306a36Sopenharmony_ci		}
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	hbdev->mtd = do_map_probe("cfi_probe", map);
11062306a36Sopenharmony_ci	if (!hbdev->mtd) {
11162306a36Sopenharmony_ci		dev_err(dev, "probing of hyperbus device failed\n");
11262306a36Sopenharmony_ci		return -ENODEV;
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	hbdev->mtd->dev.parent = dev;
11662306a36Sopenharmony_ci	mtd_set_of_node(hbdev->mtd, np);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	ret = mtd_device_register(hbdev->mtd, NULL, 0);
11962306a36Sopenharmony_ci	if (ret) {
12062306a36Sopenharmony_ci		dev_err(dev, "failed to register mtd device\n");
12162306a36Sopenharmony_ci		map_destroy(hbdev->mtd);
12262306a36Sopenharmony_ci		return ret;
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	return 0;
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(hyperbus_register_device);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_civoid hyperbus_unregister_device(struct hyperbus_device *hbdev)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	if (hbdev && hbdev->mtd) {
13262306a36Sopenharmony_ci		WARN_ON(mtd_device_unregister(hbdev->mtd));
13362306a36Sopenharmony_ci		map_destroy(hbdev->mtd);
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(hyperbus_unregister_device);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ciMODULE_DESCRIPTION("HyperBus Framework");
13962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
14062306a36Sopenharmony_ciMODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>");
141