diff options
Diffstat (limited to 'drivers/mtd/hyperbus')
-rw-r--r-- | drivers/mtd/hyperbus/Kconfig | 23 | ||||
-rw-r--r-- | drivers/mtd/hyperbus/Makefile | 4 | ||||
-rw-r--r-- | drivers/mtd/hyperbus/hbmc-am654.c | 147 | ||||
-rw-r--r-- | drivers/mtd/hyperbus/hyperbus-core.c | 153 |
4 files changed, 327 insertions, 0 deletions
diff --git a/drivers/mtd/hyperbus/Kconfig b/drivers/mtd/hyperbus/Kconfig new file mode 100644 index 000000000000..cff6bbd226f5 --- /dev/null +++ b/drivers/mtd/hyperbus/Kconfig @@ -0,0 +1,23 @@ +menuconfig MTD_HYPERBUS + tristate "HyperBus support" + select MTD_CFI + select MTD_MAP_BANK_WIDTH_2 + select MTD_CFI_AMDSTD + select MTD_COMPLEX_MAPPINGS + help + This is the framework for the HyperBus which can be used by + the HyperBus Controller driver to communicate with + HyperFlash. See Cypress HyperBus specification for more + details + +if MTD_HYPERBUS + +config HBMC_AM654 + tristate "HyperBus controller driver for AM65x SoC" + select MULTIPLEXER + select MUX_MMIO + help + This is the driver for HyperBus controller on TI's AM65x and + other SoCs + +endif # MTD_HYPERBUS diff --git a/drivers/mtd/hyperbus/Makefile b/drivers/mtd/hyperbus/Makefile new file mode 100644 index 000000000000..8a936e066f48 --- /dev/null +++ b/drivers/mtd/hyperbus/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_MTD_HYPERBUS) += hyperbus-core.o +obj-$(CONFIG_HBMC_AM654) += hbmc-am654.o diff --git a/drivers/mtd/hyperbus/hbmc-am654.c b/drivers/mtd/hyperbus/hbmc-am654.c new file mode 100644 index 000000000000..08d543b124cd --- /dev/null +++ b/drivers/mtd/hyperbus/hbmc-am654.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/ +// Author: Vignesh Raghavendra <vigneshr@ti.com> + +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mtd/cfi.h> +#include <linux/mtd/hyperbus.h> +#include <linux/mtd/mtd.h> +#include <linux/mux/consumer.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/types.h> + +#define AM654_HBMC_CALIB_COUNT 25 + +struct am654_hbmc_priv { + struct hyperbus_ctlr ctlr; + struct hyperbus_device hbdev; + struct mux_control *mux_ctrl; +}; + +static int am654_hbmc_calibrate(struct hyperbus_device *hbdev) +{ + struct map_info *map = &hbdev->map; + struct cfi_private cfi; + int count = AM654_HBMC_CALIB_COUNT; + int pass_count = 0; + int ret; + + cfi.interleave = 1; + cfi.device_type = CFI_DEVICETYPE_X16; + cfi_send_gen_cmd(0xF0, 0, 0, map, &cfi, cfi.device_type, NULL); + cfi_send_gen_cmd(0x98, 0x55, 0, map, &cfi, cfi.device_type, NULL); + + while (count--) { + ret = cfi_qry_present(map, 0, &cfi); + if (ret) + pass_count++; + else + pass_count = 0; + if (pass_count == 5) + break; + } + + cfi_qry_mode_off(0, map, &cfi); + + return ret; +} + +static const struct hyperbus_ops am654_hbmc_ops = { + .calibrate = am654_hbmc_calibrate, +}; + +static int am654_hbmc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct am654_hbmc_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + if (of_property_read_bool(dev->of_node, "mux-controls")) { + struct mux_control *control = devm_mux_control_get(dev, NULL); + + if (IS_ERR(control)) + return PTR_ERR(control); + + ret = mux_control_select(control, 1); + if (ret) { + dev_err(dev, "Failed to select HBMC mux\n"); + return ret; + } + priv->mux_ctrl = control; + } + + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + goto disable_pm; + } + + priv->ctlr.dev = dev; + priv->ctlr.ops = &am654_hbmc_ops; + priv->hbdev.ctlr = &priv->ctlr; + priv->hbdev.np = of_get_next_child(dev->of_node, NULL); + ret = hyperbus_register_device(&priv->hbdev); + if (ret) { + dev_err(dev, "failed to register controller\n"); + pm_runtime_put_sync(&pdev->dev); + goto disable_pm; + } + + return 0; +disable_pm: + pm_runtime_disable(dev); + if (priv->mux_ctrl) + mux_control_deselect(priv->mux_ctrl); + return ret; +} + +static int am654_hbmc_remove(struct platform_device *pdev) +{ + struct am654_hbmc_priv *priv = platform_get_drvdata(pdev); + int ret; + + ret = hyperbus_unregister_device(&priv->hbdev); + if (priv->mux_ctrl) + mux_control_deselect(priv->mux_ctrl); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static const struct of_device_id am654_hbmc_dt_ids[] = { + { + .compatible = "ti,am654-hbmc", + }, + { /* end of table */ } +}; + +MODULE_DEVICE_TABLE(of, am654_hbmc_dt_ids); + +static struct platform_driver am654_hbmc_platform_driver = { + .probe = am654_hbmc_probe, + .remove = am654_hbmc_remove, + .driver = { + .name = "hbmc-am654", + .of_match_table = am654_hbmc_dt_ids, + }, +}; + +module_platform_driver(am654_hbmc_platform_driver); + +MODULE_DESCRIPTION("HBMC driver for AM654 SoC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:hbmc-am654"); +MODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>"); diff --git a/drivers/mtd/hyperbus/hyperbus-core.c b/drivers/mtd/hyperbus/hyperbus-core.c new file mode 100644 index 000000000000..6af9ea34117d --- /dev/null +++ b/drivers/mtd/hyperbus/hyperbus-core.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/ +// Author: Vignesh Raghavendra <vigneshr@ti.com> + +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mtd/hyperbus.h> +#include <linux/mtd/map.h> +#include <linux/mtd/mtd.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/types.h> + +static struct hyperbus_device *map_to_hbdev(struct map_info *map) +{ + return container_of(map, struct hyperbus_device, map); +} + +static map_word hyperbus_read16(struct map_info *map, unsigned long addr) +{ + struct hyperbus_device *hbdev = map_to_hbdev(map); + struct hyperbus_ctlr *ctlr = hbdev->ctlr; + map_word read_data; + + read_data.x[0] = ctlr->ops->read16(hbdev, addr); + + return read_data; +} + +static void hyperbus_write16(struct map_info *map, map_word d, + unsigned long addr) +{ + struct hyperbus_device *hbdev = map_to_hbdev(map); + struct hyperbus_ctlr *ctlr = hbdev->ctlr; + + ctlr->ops->write16(hbdev, addr, d.x[0]); +} + +static void hyperbus_copy_from(struct map_info *map, void *to, + unsigned long from, ssize_t len) +{ + struct hyperbus_device *hbdev = map_to_hbdev(map); + struct hyperbus_ctlr *ctlr = hbdev->ctlr; + + ctlr->ops->copy_from(hbdev, to, from, len); +} + +static void hyperbus_copy_to(struct map_info *map, unsigned long to, + const void *from, ssize_t len) +{ + struct hyperbus_device *hbdev = map_to_hbdev(map); + struct hyperbus_ctlr *ctlr = hbdev->ctlr; + + ctlr->ops->copy_to(hbdev, to, from, len); +} + +int hyperbus_register_device(struct hyperbus_device *hbdev) +{ + const struct hyperbus_ops *ops; + struct hyperbus_ctlr *ctlr; + struct device_node *np; + struct map_info *map; + struct resource res; + struct device *dev; + int ret; + + if (!hbdev || !hbdev->np || !hbdev->ctlr || !hbdev->ctlr->dev) { + pr_err("hyperbus: please fill all the necessary fields!\n"); + return -EINVAL; + } + + np = hbdev->np; + ctlr = hbdev->ctlr; + if (!of_device_is_compatible(np, "cypress,hyperflash")) + return -ENODEV; + + hbdev->memtype = HYPERFLASH; + + ret = of_address_to_resource(np, 0, &res); + if (ret) + return ret; + + dev = ctlr->dev; + map = &hbdev->map; + map->size = resource_size(&res); + map->virt = devm_ioremap_resource(dev, &res); + if (IS_ERR(map->virt)) + return PTR_ERR(map->virt); + + map->name = dev_name(dev); + map->bankwidth = 2; + map->device_node = np; + + simple_map_init(map); + ops = ctlr->ops; + if (ops) { + if (ops->read16) + map->read = hyperbus_read16; + if (ops->write16) + map->write = hyperbus_write16; + if (ops->copy_to) + map->copy_to = hyperbus_copy_to; + if (ops->copy_from) + map->copy_from = hyperbus_copy_from; + + if (ops->calibrate && !ctlr->calibrated) { + ret = ops->calibrate(hbdev); + if (!ret) { + dev_err(dev, "Calibration failed\n"); + return -ENODEV; + } + ctlr->calibrated = true; + } + } + + hbdev->mtd = do_map_probe("cfi_probe", map); + if (!hbdev->mtd) { + dev_err(dev, "probing of hyperbus device failed\n"); + return -ENODEV; + } + + hbdev->mtd->dev.parent = dev; + mtd_set_of_node(hbdev->mtd, np); + + ret = mtd_device_register(hbdev->mtd, NULL, 0); + if (ret) { + dev_err(dev, "failed to register mtd device\n"); + map_destroy(hbdev->mtd); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(hyperbus_register_device); + +int hyperbus_unregister_device(struct hyperbus_device *hbdev) +{ + int ret = 0; + + if (hbdev && hbdev->mtd) { + ret = mtd_device_unregister(hbdev->mtd); + map_destroy(hbdev->mtd); + } + + return ret; +} +EXPORT_SYMBOL_GPL(hyperbus_unregister_device); + +MODULE_DESCRIPTION("HyperBus Framework"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>"); |