18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2018, Nuvoton Corporation.
48c2ecf20Sopenharmony_ci * Copyright (c) 2018, Intel Corporation.
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "nuvoton-kcs-bmc: " fmt
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/atomic.h>
108c2ecf20Sopenharmony_ci#include <linux/errno.h>
118c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
128c2ecf20Sopenharmony_ci#include <linux/io.h>
138c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h>
148c2ecf20Sopenharmony_ci#include <linux/module.h>
158c2ecf20Sopenharmony_ci#include <linux/of.h>
168c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
178c2ecf20Sopenharmony_ci#include <linux/regmap.h>
188c2ecf20Sopenharmony_ci#include <linux/slab.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#include "kcs_bmc.h"
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#define DEVICE_NAME	"npcm-kcs-bmc"
238c2ecf20Sopenharmony_ci#define KCS_CHANNEL_MAX	3
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define KCS1ST		0x0C
268c2ecf20Sopenharmony_ci#define KCS2ST		0x1E
278c2ecf20Sopenharmony_ci#define KCS3ST		0x30
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define KCS1DO		0x0E
308c2ecf20Sopenharmony_ci#define KCS2DO		0x20
318c2ecf20Sopenharmony_ci#define KCS3DO		0x32
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define KCS1DI		0x10
348c2ecf20Sopenharmony_ci#define KCS2DI		0x22
358c2ecf20Sopenharmony_ci#define KCS3DI		0x34
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci#define KCS1CTL		0x18
388c2ecf20Sopenharmony_ci#define KCS2CTL		0x2A
398c2ecf20Sopenharmony_ci#define KCS3CTL		0x3C
408c2ecf20Sopenharmony_ci#define    KCS_CTL_IBFIE	BIT(0)
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#define KCS1IE		0x1C
438c2ecf20Sopenharmony_ci#define KCS2IE		0x2E
448c2ecf20Sopenharmony_ci#define KCS3IE		0x40
458c2ecf20Sopenharmony_ci#define    KCS_IE_IRQE          BIT(0)
468c2ecf20Sopenharmony_ci#define    KCS_IE_HIRQE         BIT(3)
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci/*
498c2ecf20Sopenharmony_ci * 7.2.4 Core KCS Registers
508c2ecf20Sopenharmony_ci * Registers in this module are 8 bits. An 8-bit register must be accessed
518c2ecf20Sopenharmony_ci * by an 8-bit read or write.
528c2ecf20Sopenharmony_ci *
538c2ecf20Sopenharmony_ci * sts: KCS Channel n Status Register (KCSnST).
548c2ecf20Sopenharmony_ci * dob: KCS Channel n Data Out Buffer Register (KCSnDO).
558c2ecf20Sopenharmony_ci * dib: KCS Channel n Data In Buffer Register (KCSnDI).
568c2ecf20Sopenharmony_ci * ctl: KCS Channel n Control Register (KCSnCTL).
578c2ecf20Sopenharmony_ci * ie : KCS Channel n  Interrupt Enable Register (KCSnIE).
588c2ecf20Sopenharmony_ci */
598c2ecf20Sopenharmony_cistruct npcm7xx_kcs_reg {
608c2ecf20Sopenharmony_ci	u32 sts;
618c2ecf20Sopenharmony_ci	u32 dob;
628c2ecf20Sopenharmony_ci	u32 dib;
638c2ecf20Sopenharmony_ci	u32 ctl;
648c2ecf20Sopenharmony_ci	u32 ie;
658c2ecf20Sopenharmony_ci};
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistruct npcm7xx_kcs_bmc {
688c2ecf20Sopenharmony_ci	struct regmap *map;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	const struct npcm7xx_kcs_reg *reg;
718c2ecf20Sopenharmony_ci};
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = {
748c2ecf20Sopenharmony_ci	{ .sts = KCS1ST, .dob = KCS1DO, .dib = KCS1DI, .ctl = KCS1CTL, .ie = KCS1IE },
758c2ecf20Sopenharmony_ci	{ .sts = KCS2ST, .dob = KCS2DO, .dib = KCS2DI, .ctl = KCS2CTL, .ie = KCS2IE },
768c2ecf20Sopenharmony_ci	{ .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE },
778c2ecf20Sopenharmony_ci};
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_cistatic u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
808c2ecf20Sopenharmony_ci{
818c2ecf20Sopenharmony_ci	struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
828c2ecf20Sopenharmony_ci	u32 val = 0;
838c2ecf20Sopenharmony_ci	int rc;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	rc = regmap_read(priv->map, reg, &val);
868c2ecf20Sopenharmony_ci	WARN(rc != 0, "regmap_read() failed: %d\n", rc);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	return rc == 0 ? (u8)val : 0;
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
948c2ecf20Sopenharmony_ci	int rc;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	rc = regmap_write(priv->map, reg, data);
978c2ecf20Sopenharmony_ci	WARN(rc != 0, "regmap_write() failed: %d\n", rc);
988c2ecf20Sopenharmony_ci}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_cistatic void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
1018c2ecf20Sopenharmony_ci{
1028c2ecf20Sopenharmony_ci	struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
1058c2ecf20Sopenharmony_ci			   enable ? KCS_CTL_IBFIE : 0);
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	regmap_update_bits(priv->map, priv->reg->ie, KCS_IE_IRQE | KCS_IE_HIRQE,
1088c2ecf20Sopenharmony_ci			   enable ? KCS_IE_IRQE | KCS_IE_HIRQE : 0);
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
1128c2ecf20Sopenharmony_ci{
1138c2ecf20Sopenharmony_ci	struct kcs_bmc *kcs_bmc = arg;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	if (!kcs_bmc_handle_event(kcs_bmc))
1168c2ecf20Sopenharmony_ci		return IRQ_HANDLED;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	return IRQ_NONE;
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic int npcm7xx_kcs_config_irq(struct kcs_bmc *kcs_bmc,
1228c2ecf20Sopenharmony_ci				  struct platform_device *pdev)
1238c2ecf20Sopenharmony_ci{
1248c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
1258c2ecf20Sopenharmony_ci	int irq;
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
1288c2ecf20Sopenharmony_ci	if (irq < 0)
1298c2ecf20Sopenharmony_ci		return irq;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	return devm_request_irq(dev, irq, npcm7xx_kcs_irq, IRQF_SHARED,
1328c2ecf20Sopenharmony_ci				dev_name(dev), kcs_bmc);
1338c2ecf20Sopenharmony_ci}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_cistatic int npcm7xx_kcs_probe(struct platform_device *pdev)
1368c2ecf20Sopenharmony_ci{
1378c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
1388c2ecf20Sopenharmony_ci	struct npcm7xx_kcs_bmc *priv;
1398c2ecf20Sopenharmony_ci	struct kcs_bmc *kcs_bmc;
1408c2ecf20Sopenharmony_ci	u32 chan;
1418c2ecf20Sopenharmony_ci	int rc;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	rc = of_property_read_u32(dev->of_node, "kcs_chan", &chan);
1448c2ecf20Sopenharmony_ci	if (rc != 0 || chan == 0 || chan > KCS_CHANNEL_MAX) {
1458c2ecf20Sopenharmony_ci		dev_err(dev, "no valid 'kcs_chan' configured\n");
1468c2ecf20Sopenharmony_ci		return -ENODEV;
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	kcs_bmc = kcs_bmc_alloc(dev, sizeof(*priv), chan);
1508c2ecf20Sopenharmony_ci	if (!kcs_bmc)
1518c2ecf20Sopenharmony_ci		return -ENOMEM;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	priv = kcs_bmc_priv(kcs_bmc);
1548c2ecf20Sopenharmony_ci	priv->map = syscon_node_to_regmap(dev->parent->of_node);
1558c2ecf20Sopenharmony_ci	if (IS_ERR(priv->map)) {
1568c2ecf20Sopenharmony_ci		dev_err(dev, "Couldn't get regmap\n");
1578c2ecf20Sopenharmony_ci		return -ENODEV;
1588c2ecf20Sopenharmony_ci	}
1598c2ecf20Sopenharmony_ci	priv->reg = &npcm7xx_kcs_reg_tbl[chan - 1];
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	kcs_bmc->ioreg.idr = priv->reg->dib;
1628c2ecf20Sopenharmony_ci	kcs_bmc->ioreg.odr = priv->reg->dob;
1638c2ecf20Sopenharmony_ci	kcs_bmc->ioreg.str = priv->reg->sts;
1648c2ecf20Sopenharmony_ci	kcs_bmc->io_inputb = npcm7xx_kcs_inb;
1658c2ecf20Sopenharmony_ci	kcs_bmc->io_outputb = npcm7xx_kcs_outb;
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, kcs_bmc);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	npcm7xx_kcs_enable_channel(kcs_bmc, true);
1708c2ecf20Sopenharmony_ci	rc = npcm7xx_kcs_config_irq(kcs_bmc, pdev);
1718c2ecf20Sopenharmony_ci	if (rc)
1728c2ecf20Sopenharmony_ci		return rc;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	rc = misc_register(&kcs_bmc->miscdev);
1758c2ecf20Sopenharmony_ci	if (rc) {
1768c2ecf20Sopenharmony_ci		dev_err(dev, "Unable to register device\n");
1778c2ecf20Sopenharmony_ci		return rc;
1788c2ecf20Sopenharmony_ci	}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	pr_info("channel=%u idr=0x%x odr=0x%x str=0x%x\n",
1818c2ecf20Sopenharmony_ci		chan,
1828c2ecf20Sopenharmony_ci		kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr, kcs_bmc->ioreg.str);
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	return 0;
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_cistatic int npcm7xx_kcs_remove(struct platform_device *pdev)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	misc_deregister(&kcs_bmc->miscdev);
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	return 0;
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_cistatic const struct of_device_id npcm_kcs_bmc_match[] = {
1978c2ecf20Sopenharmony_ci	{ .compatible = "nuvoton,npcm750-kcs-bmc" },
1988c2ecf20Sopenharmony_ci	{ }
1998c2ecf20Sopenharmony_ci};
2008c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, npcm_kcs_bmc_match);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_cistatic struct platform_driver npcm_kcs_bmc_driver = {
2038c2ecf20Sopenharmony_ci	.driver = {
2048c2ecf20Sopenharmony_ci		.name		= DEVICE_NAME,
2058c2ecf20Sopenharmony_ci		.of_match_table	= npcm_kcs_bmc_match,
2068c2ecf20Sopenharmony_ci	},
2078c2ecf20Sopenharmony_ci	.probe	= npcm7xx_kcs_probe,
2088c2ecf20Sopenharmony_ci	.remove	= npcm7xx_kcs_remove,
2098c2ecf20Sopenharmony_ci};
2108c2ecf20Sopenharmony_cimodule_platform_driver(npcm_kcs_bmc_driver);
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
2138c2ecf20Sopenharmony_ciMODULE_AUTHOR("Avi Fishman <avifishman70@gmail.com>");
2148c2ecf20Sopenharmony_ciMODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
2158c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("NPCM7xx device interface to the KCS BMC device");
216