162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2018, Nuvoton Corporation.
462306a36Sopenharmony_ci * Copyright (c) 2018, Intel Corporation.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#define pr_fmt(fmt) "nuvoton-kcs-bmc: " fmt
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/atomic.h>
1062306a36Sopenharmony_ci#include <linux/errno.h>
1162306a36Sopenharmony_ci#include <linux/interrupt.h>
1262306a36Sopenharmony_ci#include <linux/io.h>
1362306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/of.h>
1662306a36Sopenharmony_ci#include <linux/platform_device.h>
1762306a36Sopenharmony_ci#include <linux/regmap.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include "kcs_bmc_device.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define DEVICE_NAME	"npcm-kcs-bmc"
2362306a36Sopenharmony_ci#define KCS_CHANNEL_MAX	3
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define KCS1ST		0x0C
2662306a36Sopenharmony_ci#define KCS2ST		0x1E
2762306a36Sopenharmony_ci#define KCS3ST		0x30
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define KCS1DO		0x0E
3062306a36Sopenharmony_ci#define KCS2DO		0x20
3162306a36Sopenharmony_ci#define KCS3DO		0x32
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define KCS1DI		0x10
3462306a36Sopenharmony_ci#define KCS2DI		0x22
3562306a36Sopenharmony_ci#define KCS3DI		0x34
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define KCS1CTL		0x18
3862306a36Sopenharmony_ci#define KCS2CTL		0x2A
3962306a36Sopenharmony_ci#define KCS3CTL		0x3C
4062306a36Sopenharmony_ci#define    KCS_CTL_IBFIE	BIT(0)
4162306a36Sopenharmony_ci#define    KCS_CTL_OBEIE	BIT(1)
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci#define KCS1IE		0x1C
4462306a36Sopenharmony_ci#define KCS2IE		0x2E
4562306a36Sopenharmony_ci#define KCS3IE		0x40
4662306a36Sopenharmony_ci#define    KCS_IE_IRQE          BIT(0)
4762306a36Sopenharmony_ci#define    KCS_IE_HIRQE         BIT(3)
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci/*
5062306a36Sopenharmony_ci * 7.2.4 Core KCS Registers
5162306a36Sopenharmony_ci * Registers in this module are 8 bits. An 8-bit register must be accessed
5262306a36Sopenharmony_ci * by an 8-bit read or write.
5362306a36Sopenharmony_ci *
5462306a36Sopenharmony_ci * sts: KCS Channel n Status Register (KCSnST).
5562306a36Sopenharmony_ci * dob: KCS Channel n Data Out Buffer Register (KCSnDO).
5662306a36Sopenharmony_ci * dib: KCS Channel n Data In Buffer Register (KCSnDI).
5762306a36Sopenharmony_ci * ctl: KCS Channel n Control Register (KCSnCTL).
5862306a36Sopenharmony_ci * ie : KCS Channel n  Interrupt Enable Register (KCSnIE).
5962306a36Sopenharmony_ci */
6062306a36Sopenharmony_cistruct npcm7xx_kcs_reg {
6162306a36Sopenharmony_ci	u32 sts;
6262306a36Sopenharmony_ci	u32 dob;
6362306a36Sopenharmony_ci	u32 dib;
6462306a36Sopenharmony_ci	u32 ctl;
6562306a36Sopenharmony_ci	u32 ie;
6662306a36Sopenharmony_ci};
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistruct npcm7xx_kcs_bmc {
6962306a36Sopenharmony_ci	struct kcs_bmc_device kcs_bmc;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	struct regmap *map;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	const struct npcm7xx_kcs_reg *reg;
7462306a36Sopenharmony_ci};
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = {
7762306a36Sopenharmony_ci	{ .sts = KCS1ST, .dob = KCS1DO, .dib = KCS1DI, .ctl = KCS1CTL, .ie = KCS1IE },
7862306a36Sopenharmony_ci	{ .sts = KCS2ST, .dob = KCS2DO, .dib = KCS2DI, .ctl = KCS2CTL, .ie = KCS2IE },
7962306a36Sopenharmony_ci	{ .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE },
8062306a36Sopenharmony_ci};
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic inline struct npcm7xx_kcs_bmc *to_npcm7xx_kcs_bmc(struct kcs_bmc_device *kcs_bmc)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	return container_of(kcs_bmc, struct npcm7xx_kcs_bmc, kcs_bmc);
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic u8 npcm7xx_kcs_inb(struct kcs_bmc_device *kcs_bmc, u32 reg)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
9062306a36Sopenharmony_ci	u32 val = 0;
9162306a36Sopenharmony_ci	int rc;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	rc = regmap_read(priv->map, reg, &val);
9462306a36Sopenharmony_ci	WARN(rc != 0, "regmap_read() failed: %d\n", rc);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	return rc == 0 ? (u8)val : 0;
9762306a36Sopenharmony_ci}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_cistatic void npcm7xx_kcs_outb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 data)
10062306a36Sopenharmony_ci{
10162306a36Sopenharmony_ci	struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
10262306a36Sopenharmony_ci	int rc;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	rc = regmap_write(priv->map, reg, data);
10562306a36Sopenharmony_ci	WARN(rc != 0, "regmap_write() failed: %d\n", rc);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic void npcm7xx_kcs_updateb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask, u8 data)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
11162306a36Sopenharmony_ci	int rc;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	rc = regmap_update_bits(priv->map, reg, mask, data);
11462306a36Sopenharmony_ci	WARN(rc != 0, "regmap_update_bits() failed: %d\n", rc);
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic void npcm7xx_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enable)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	regmap_update_bits(priv->map, priv->reg->ie, KCS_IE_IRQE | KCS_IE_HIRQE,
12262306a36Sopenharmony_ci			   enable ? KCS_IE_IRQE | KCS_IE_HIRQE : 0);
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic void npcm7xx_kcs_irq_mask_update(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 state)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	if (mask & KCS_BMC_EVENT_TYPE_OBE)
13062306a36Sopenharmony_ci		regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_OBEIE,
13162306a36Sopenharmony_ci				   !!(state & KCS_BMC_EVENT_TYPE_OBE) * KCS_CTL_OBEIE);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	if (mask & KCS_BMC_EVENT_TYPE_IBF)
13462306a36Sopenharmony_ci		regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
13562306a36Sopenharmony_ci				   !!(state & KCS_BMC_EVENT_TYPE_IBF) * KCS_CTL_IBFIE);
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	struct kcs_bmc_device *kcs_bmc = arg;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	return kcs_bmc_handle_event(kcs_bmc);
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic int npcm7xx_kcs_config_irq(struct kcs_bmc_device *kcs_bmc,
14662306a36Sopenharmony_ci				  struct platform_device *pdev)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
14962306a36Sopenharmony_ci	int irq;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
15262306a36Sopenharmony_ci	if (irq < 0)
15362306a36Sopenharmony_ci		return irq;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	return devm_request_irq(dev, irq, npcm7xx_kcs_irq, IRQF_SHARED,
15662306a36Sopenharmony_ci				dev_name(dev), kcs_bmc);
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic const struct kcs_bmc_device_ops npcm7xx_kcs_ops = {
16062306a36Sopenharmony_ci	.irq_mask_update = npcm7xx_kcs_irq_mask_update,
16162306a36Sopenharmony_ci	.io_inputb = npcm7xx_kcs_inb,
16262306a36Sopenharmony_ci	.io_outputb = npcm7xx_kcs_outb,
16362306a36Sopenharmony_ci	.io_updateb = npcm7xx_kcs_updateb,
16462306a36Sopenharmony_ci};
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_cistatic int npcm7xx_kcs_probe(struct platform_device *pdev)
16762306a36Sopenharmony_ci{
16862306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
16962306a36Sopenharmony_ci	struct npcm7xx_kcs_bmc *priv;
17062306a36Sopenharmony_ci	struct kcs_bmc_device *kcs_bmc;
17162306a36Sopenharmony_ci	u32 chan;
17262306a36Sopenharmony_ci	int rc;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	rc = of_property_read_u32(dev->of_node, "kcs_chan", &chan);
17562306a36Sopenharmony_ci	if (rc != 0 || chan == 0 || chan > KCS_CHANNEL_MAX) {
17662306a36Sopenharmony_ci		dev_err(dev, "no valid 'kcs_chan' configured\n");
17762306a36Sopenharmony_ci		return -ENODEV;
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
18162306a36Sopenharmony_ci	if (!priv)
18262306a36Sopenharmony_ci		return -ENOMEM;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	priv->map = syscon_node_to_regmap(dev->parent->of_node);
18562306a36Sopenharmony_ci	if (IS_ERR(priv->map)) {
18662306a36Sopenharmony_ci		dev_err(dev, "Couldn't get regmap\n");
18762306a36Sopenharmony_ci		return -ENODEV;
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci	priv->reg = &npcm7xx_kcs_reg_tbl[chan - 1];
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	kcs_bmc = &priv->kcs_bmc;
19262306a36Sopenharmony_ci	kcs_bmc->dev = &pdev->dev;
19362306a36Sopenharmony_ci	kcs_bmc->channel = chan;
19462306a36Sopenharmony_ci	kcs_bmc->ioreg.idr = priv->reg->dib;
19562306a36Sopenharmony_ci	kcs_bmc->ioreg.odr = priv->reg->dob;
19662306a36Sopenharmony_ci	kcs_bmc->ioreg.str = priv->reg->sts;
19762306a36Sopenharmony_ci	kcs_bmc->ops = &npcm7xx_kcs_ops;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	platform_set_drvdata(pdev, priv);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	rc = npcm7xx_kcs_config_irq(kcs_bmc, pdev);
20262306a36Sopenharmony_ci	if (rc)
20362306a36Sopenharmony_ci		return rc;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	npcm7xx_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0);
20662306a36Sopenharmony_ci	npcm7xx_kcs_enable_channel(kcs_bmc, true);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	rc = kcs_bmc_add_device(kcs_bmc);
20962306a36Sopenharmony_ci	if (rc) {
21062306a36Sopenharmony_ci		dev_warn(&pdev->dev, "Failed to register channel %d: %d\n", kcs_bmc->channel, rc);
21162306a36Sopenharmony_ci		return rc;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	pr_info("channel=%u idr=0x%x odr=0x%x str=0x%x\n",
21562306a36Sopenharmony_ci		chan,
21662306a36Sopenharmony_ci		kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr, kcs_bmc->ioreg.str);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	return 0;
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cistatic int npcm7xx_kcs_remove(struct platform_device *pdev)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	struct npcm7xx_kcs_bmc *priv = platform_get_drvdata(pdev);
22462306a36Sopenharmony_ci	struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	kcs_bmc_remove_device(kcs_bmc);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	npcm7xx_kcs_enable_channel(kcs_bmc, false);
22962306a36Sopenharmony_ci	npcm7xx_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	return 0;
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_cistatic const struct of_device_id npcm_kcs_bmc_match[] = {
23562306a36Sopenharmony_ci	{ .compatible = "nuvoton,npcm750-kcs-bmc" },
23662306a36Sopenharmony_ci	{ }
23762306a36Sopenharmony_ci};
23862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, npcm_kcs_bmc_match);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_cistatic struct platform_driver npcm_kcs_bmc_driver = {
24162306a36Sopenharmony_ci	.driver = {
24262306a36Sopenharmony_ci		.name		= DEVICE_NAME,
24362306a36Sopenharmony_ci		.of_match_table	= npcm_kcs_bmc_match,
24462306a36Sopenharmony_ci	},
24562306a36Sopenharmony_ci	.probe	= npcm7xx_kcs_probe,
24662306a36Sopenharmony_ci	.remove	= npcm7xx_kcs_remove,
24762306a36Sopenharmony_ci};
24862306a36Sopenharmony_cimodule_platform_driver(npcm_kcs_bmc_driver);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
25162306a36Sopenharmony_ciMODULE_AUTHOR("Avi Fishman <avifishman70@gmail.com>");
25262306a36Sopenharmony_ciMODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
25362306a36Sopenharmony_ciMODULE_DESCRIPTION("NPCM7xx device interface to the KCS BMC device");
254