18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Amlogic Meson GX eFuse Driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2016 Endless Computers, Inc.
68c2ecf20Sopenharmony_ci * Author: Carlo Caione <carlo@endlessm.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/clk.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/nvmem-provider.h>
128c2ecf20Sopenharmony_ci#include <linux/of.h>
138c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/firmware/meson/meson_sm.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_cistatic int meson_efuse_read(void *context, unsigned int offset,
188c2ecf20Sopenharmony_ci			    void *val, size_t bytes)
198c2ecf20Sopenharmony_ci{
208c2ecf20Sopenharmony_ci	struct meson_sm_firmware *fw = context;
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci	return meson_sm_call_read(fw, (u8 *)val, bytes, SM_EFUSE_READ, offset,
238c2ecf20Sopenharmony_ci				  bytes, 0, 0, 0);
248c2ecf20Sopenharmony_ci}
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic int meson_efuse_write(void *context, unsigned int offset,
278c2ecf20Sopenharmony_ci			     void *val, size_t bytes)
288c2ecf20Sopenharmony_ci{
298c2ecf20Sopenharmony_ci	struct meson_sm_firmware *fw = context;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci	return meson_sm_call_write(fw, (u8 *)val, bytes, SM_EFUSE_WRITE, offset,
328c2ecf20Sopenharmony_ci				   bytes, 0, 0, 0);
338c2ecf20Sopenharmony_ci}
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistatic const struct of_device_id meson_efuse_match[] = {
368c2ecf20Sopenharmony_ci	{ .compatible = "amlogic,meson-gxbb-efuse", },
378c2ecf20Sopenharmony_ci	{ /* sentinel */ },
388c2ecf20Sopenharmony_ci};
398c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, meson_efuse_match);
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_cistatic int meson_efuse_probe(struct platform_device *pdev)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
448c2ecf20Sopenharmony_ci	struct meson_sm_firmware *fw;
458c2ecf20Sopenharmony_ci	struct device_node *sm_np;
468c2ecf20Sopenharmony_ci	struct nvmem_device *nvmem;
478c2ecf20Sopenharmony_ci	struct nvmem_config *econfig;
488c2ecf20Sopenharmony_ci	struct clk *clk;
498c2ecf20Sopenharmony_ci	unsigned int size;
508c2ecf20Sopenharmony_ci	int ret;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	sm_np = of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0);
538c2ecf20Sopenharmony_ci	if (!sm_np) {
548c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "no secure-monitor node\n");
558c2ecf20Sopenharmony_ci		return -ENODEV;
568c2ecf20Sopenharmony_ci	}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	fw = meson_sm_get(sm_np);
598c2ecf20Sopenharmony_ci	of_node_put(sm_np);
608c2ecf20Sopenharmony_ci	if (!fw)
618c2ecf20Sopenharmony_ci		return -EPROBE_DEFER;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	clk = devm_clk_get(dev, NULL);
648c2ecf20Sopenharmony_ci	if (IS_ERR(clk)) {
658c2ecf20Sopenharmony_ci		ret = PTR_ERR(clk);
668c2ecf20Sopenharmony_ci		if (ret != -EPROBE_DEFER)
678c2ecf20Sopenharmony_ci			dev_err(dev, "failed to get efuse gate");
688c2ecf20Sopenharmony_ci		return ret;
698c2ecf20Sopenharmony_ci	}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	ret = clk_prepare_enable(clk);
728c2ecf20Sopenharmony_ci	if (ret) {
738c2ecf20Sopenharmony_ci		dev_err(dev, "failed to enable gate");
748c2ecf20Sopenharmony_ci		return ret;
758c2ecf20Sopenharmony_ci	}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	ret = devm_add_action_or_reset(dev,
788c2ecf20Sopenharmony_ci				       (void(*)(void *))clk_disable_unprepare,
798c2ecf20Sopenharmony_ci				       clk);
808c2ecf20Sopenharmony_ci	if (ret) {
818c2ecf20Sopenharmony_ci		dev_err(dev, "failed to add disable callback");
828c2ecf20Sopenharmony_ci		return ret;
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	if (meson_sm_call(fw, SM_EFUSE_USER_MAX, &size, 0, 0, 0, 0, 0) < 0) {
868c2ecf20Sopenharmony_ci		dev_err(dev, "failed to get max user");
878c2ecf20Sopenharmony_ci		return -EINVAL;
888c2ecf20Sopenharmony_ci	}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	econfig = devm_kzalloc(dev, sizeof(*econfig), GFP_KERNEL);
918c2ecf20Sopenharmony_ci	if (!econfig)
928c2ecf20Sopenharmony_ci		return -ENOMEM;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	econfig->dev = dev;
958c2ecf20Sopenharmony_ci	econfig->name = dev_name(dev);
968c2ecf20Sopenharmony_ci	econfig->stride = 1;
978c2ecf20Sopenharmony_ci	econfig->word_size = 1;
988c2ecf20Sopenharmony_ci	econfig->reg_read = meson_efuse_read;
998c2ecf20Sopenharmony_ci	econfig->reg_write = meson_efuse_write;
1008c2ecf20Sopenharmony_ci	econfig->size = size;
1018c2ecf20Sopenharmony_ci	econfig->priv = fw;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	nvmem = devm_nvmem_register(&pdev->dev, econfig);
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	return PTR_ERR_OR_ZERO(nvmem);
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cistatic struct platform_driver meson_efuse_driver = {
1098c2ecf20Sopenharmony_ci	.probe = meson_efuse_probe,
1108c2ecf20Sopenharmony_ci	.driver = {
1118c2ecf20Sopenharmony_ci		.name = "meson-efuse",
1128c2ecf20Sopenharmony_ci		.of_match_table = meson_efuse_match,
1138c2ecf20Sopenharmony_ci	},
1148c2ecf20Sopenharmony_ci};
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cimodule_platform_driver(meson_efuse_driver);
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ciMODULE_AUTHOR("Carlo Caione <carlo@endlessm.com>");
1198c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Amlogic Meson GX NVMEM driver");
1208c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
121