xref: /linux/drivers/char/ipmi/kcs_bmc_npcm7xx.c (revision 2c1ed907520c50326b8f604907a8478b27881a2e)
16b2e54f7SHaiyue Wang // SPDX-License-Identifier: GPL-2.0
26b2e54f7SHaiyue Wang /*
36b2e54f7SHaiyue Wang  * Copyright (c) 2018, Nuvoton Corporation.
46b2e54f7SHaiyue Wang  * Copyright (c) 2018, Intel Corporation.
56b2e54f7SHaiyue Wang  */
66b2e54f7SHaiyue Wang 
76b2e54f7SHaiyue Wang #define pr_fmt(fmt) "nuvoton-kcs-bmc: " fmt
86b2e54f7SHaiyue Wang 
96b2e54f7SHaiyue Wang #include <linux/atomic.h>
106b2e54f7SHaiyue Wang #include <linux/errno.h>
116b2e54f7SHaiyue Wang #include <linux/interrupt.h>
126b2e54f7SHaiyue Wang #include <linux/io.h>
136b2e54f7SHaiyue Wang #include <linux/mfd/syscon.h>
146b2e54f7SHaiyue Wang #include <linux/module.h>
156b2e54f7SHaiyue Wang #include <linux/of.h>
166b2e54f7SHaiyue Wang #include <linux/platform_device.h>
176b2e54f7SHaiyue Wang #include <linux/regmap.h>
186b2e54f7SHaiyue Wang #include <linux/slab.h>
196b2e54f7SHaiyue Wang 
20faae6e39SAndrew Jeffery #include "kcs_bmc_device.h"
216b2e54f7SHaiyue Wang 
226b2e54f7SHaiyue Wang #define DEVICE_NAME	"npcm-kcs-bmc"
236b2e54f7SHaiyue Wang #define KCS_CHANNEL_MAX	3
246b2e54f7SHaiyue Wang 
256b2e54f7SHaiyue Wang #define KCS1ST		0x0C
266b2e54f7SHaiyue Wang #define KCS2ST		0x1E
276b2e54f7SHaiyue Wang #define KCS3ST		0x30
286b2e54f7SHaiyue Wang 
296b2e54f7SHaiyue Wang #define KCS1DO		0x0E
306b2e54f7SHaiyue Wang #define KCS2DO		0x20
316b2e54f7SHaiyue Wang #define KCS3DO		0x32
326b2e54f7SHaiyue Wang 
336b2e54f7SHaiyue Wang #define KCS1DI		0x10
346b2e54f7SHaiyue Wang #define KCS2DI		0x22
356b2e54f7SHaiyue Wang #define KCS3DI		0x34
366b2e54f7SHaiyue Wang 
376b2e54f7SHaiyue Wang #define KCS1CTL		0x18
386b2e54f7SHaiyue Wang #define KCS2CTL		0x2A
396b2e54f7SHaiyue Wang #define KCS3CTL		0x3C
406b2e54f7SHaiyue Wang #define    KCS_CTL_IBFIE	BIT(0)
4128651e6cSAndrew Jeffery #define    KCS_CTL_OBEIE	BIT(1)
426b2e54f7SHaiyue Wang 
4358aae18eSAvi Fishman #define KCS1IE		0x1C
4458aae18eSAvi Fishman #define KCS2IE		0x2E
4558aae18eSAvi Fishman #define KCS3IE		0x40
4658aae18eSAvi Fishman #define    KCS_IE_IRQE          BIT(0)
4758aae18eSAvi Fishman #define    KCS_IE_HIRQE         BIT(3)
4858aae18eSAvi Fishman 
496b2e54f7SHaiyue Wang /*
506b2e54f7SHaiyue Wang  * 7.2.4 Core KCS Registers
516b2e54f7SHaiyue Wang  * Registers in this module are 8 bits. An 8-bit register must be accessed
526b2e54f7SHaiyue Wang  * by an 8-bit read or write.
536b2e54f7SHaiyue Wang  *
546b2e54f7SHaiyue Wang  * sts: KCS Channel n Status Register (KCSnST).
556b2e54f7SHaiyue Wang  * dob: KCS Channel n Data Out Buffer Register (KCSnDO).
566b2e54f7SHaiyue Wang  * dib: KCS Channel n Data In Buffer Register (KCSnDI).
576b2e54f7SHaiyue Wang  * ctl: KCS Channel n Control Register (KCSnCTL).
5858aae18eSAvi Fishman  * ie : KCS Channel n  Interrupt Enable Register (KCSnIE).
596b2e54f7SHaiyue Wang  */
606b2e54f7SHaiyue Wang struct npcm7xx_kcs_reg {
616b2e54f7SHaiyue Wang 	u32 sts;
626b2e54f7SHaiyue Wang 	u32 dob;
636b2e54f7SHaiyue Wang 	u32 dib;
646b2e54f7SHaiyue Wang 	u32 ctl;
6558aae18eSAvi Fishman 	u32 ie;
666b2e54f7SHaiyue Wang };
676b2e54f7SHaiyue Wang 
686b2e54f7SHaiyue Wang struct npcm7xx_kcs_bmc {
69d4e7ac68SAndrew Jeffery 	struct kcs_bmc_device kcs_bmc;
70d7096970SAndrew Jeffery 
716b2e54f7SHaiyue Wang 	struct regmap *map;
726b2e54f7SHaiyue Wang 
736b2e54f7SHaiyue Wang 	const struct npcm7xx_kcs_reg *reg;
746b2e54f7SHaiyue Wang };
756b2e54f7SHaiyue Wang 
766b2e54f7SHaiyue Wang static const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = {
7758aae18eSAvi Fishman 	{ .sts = KCS1ST, .dob = KCS1DO, .dib = KCS1DI, .ctl = KCS1CTL, .ie = KCS1IE },
7858aae18eSAvi Fishman 	{ .sts = KCS2ST, .dob = KCS2DO, .dib = KCS2DI, .ctl = KCS2CTL, .ie = KCS2IE },
7958aae18eSAvi Fishman 	{ .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE },
806b2e54f7SHaiyue Wang };
816b2e54f7SHaiyue Wang 
to_npcm7xx_kcs_bmc(struct kcs_bmc_device * kcs_bmc)82d4e7ac68SAndrew Jeffery static inline struct npcm7xx_kcs_bmc *to_npcm7xx_kcs_bmc(struct kcs_bmc_device *kcs_bmc)
83d7096970SAndrew Jeffery {
84d7096970SAndrew Jeffery 	return container_of(kcs_bmc, struct npcm7xx_kcs_bmc, kcs_bmc);
85d7096970SAndrew Jeffery }
86d7096970SAndrew Jeffery 
npcm7xx_kcs_inb(struct kcs_bmc_device * kcs_bmc,u32 reg)87d4e7ac68SAndrew Jeffery static u8 npcm7xx_kcs_inb(struct kcs_bmc_device *kcs_bmc, u32 reg)
886b2e54f7SHaiyue Wang {
89d7096970SAndrew Jeffery 	struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
906b2e54f7SHaiyue Wang 	u32 val = 0;
916b2e54f7SHaiyue Wang 	int rc;
926b2e54f7SHaiyue Wang 
936b2e54f7SHaiyue Wang 	rc = regmap_read(priv->map, reg, &val);
946b2e54f7SHaiyue Wang 	WARN(rc != 0, "regmap_read() failed: %d\n", rc);
956b2e54f7SHaiyue Wang 
966b2e54f7SHaiyue Wang 	return rc == 0 ? (u8)val : 0;
976b2e54f7SHaiyue Wang }
986b2e54f7SHaiyue Wang 
npcm7xx_kcs_outb(struct kcs_bmc_device * kcs_bmc,u32 reg,u8 data)99d4e7ac68SAndrew Jeffery static void npcm7xx_kcs_outb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 data)
1006b2e54f7SHaiyue Wang {
101d7096970SAndrew Jeffery 	struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
1026b2e54f7SHaiyue Wang 	int rc;
1036b2e54f7SHaiyue Wang 
1046b2e54f7SHaiyue Wang 	rc = regmap_write(priv->map, reg, data);
1056b2e54f7SHaiyue Wang 	WARN(rc != 0, "regmap_write() failed: %d\n", rc);
1066b2e54f7SHaiyue Wang }
1076b2e54f7SHaiyue Wang 
npcm7xx_kcs_updateb(struct kcs_bmc_device * kcs_bmc,u32 reg,u8 mask,u8 data)108d4e7ac68SAndrew Jeffery static void npcm7xx_kcs_updateb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask, u8 data)
109ec6f0cf1SAndrew Jeffery {
110d7096970SAndrew Jeffery 	struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
111ec6f0cf1SAndrew Jeffery 	int rc;
112ec6f0cf1SAndrew Jeffery 
113ec6f0cf1SAndrew Jeffery 	rc = regmap_update_bits(priv->map, reg, mask, data);
114ec6f0cf1SAndrew Jeffery 	WARN(rc != 0, "regmap_update_bits() failed: %d\n", rc);
115ec6f0cf1SAndrew Jeffery }
116ec6f0cf1SAndrew Jeffery 
npcm7xx_kcs_enable_channel(struct kcs_bmc_device * kcs_bmc,bool enable)117d4e7ac68SAndrew Jeffery static void npcm7xx_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enable)
1186b2e54f7SHaiyue Wang {
119d7096970SAndrew Jeffery 	struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
1206b2e54f7SHaiyue Wang 
12158aae18eSAvi Fishman 	regmap_update_bits(priv->map, priv->reg->ie, KCS_IE_IRQE | KCS_IE_HIRQE,
12258aae18eSAvi Fishman 			   enable ? KCS_IE_IRQE | KCS_IE_HIRQE : 0);
1236b2e54f7SHaiyue Wang }
1246b2e54f7SHaiyue Wang 
npcm7xx_kcs_irq_mask_update(struct kcs_bmc_device * kcs_bmc,u8 mask,u8 state)12528651e6cSAndrew Jeffery static void npcm7xx_kcs_irq_mask_update(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 state)
12628651e6cSAndrew Jeffery {
12728651e6cSAndrew Jeffery 	struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
12828651e6cSAndrew Jeffery 
12928651e6cSAndrew Jeffery 	if (mask & KCS_BMC_EVENT_TYPE_OBE)
13028651e6cSAndrew Jeffery 		regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_OBEIE,
13128651e6cSAndrew Jeffery 				   !!(state & KCS_BMC_EVENT_TYPE_OBE) * KCS_CTL_OBEIE);
13228651e6cSAndrew Jeffery 
13328651e6cSAndrew Jeffery 	if (mask & KCS_BMC_EVENT_TYPE_IBF)
13428651e6cSAndrew Jeffery 		regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
13528651e6cSAndrew Jeffery 				   !!(state & KCS_BMC_EVENT_TYPE_IBF) * KCS_CTL_IBFIE);
13628651e6cSAndrew Jeffery }
13728651e6cSAndrew Jeffery 
npcm7xx_kcs_irq(int irq,void * arg)1386b2e54f7SHaiyue Wang static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
1396b2e54f7SHaiyue Wang {
140d4e7ac68SAndrew Jeffery 	struct kcs_bmc_device *kcs_bmc = arg;
1416b2e54f7SHaiyue Wang 
142faae6e39SAndrew Jeffery 	return kcs_bmc_handle_event(kcs_bmc);
1436b2e54f7SHaiyue Wang }
1446b2e54f7SHaiyue Wang 
npcm7xx_kcs_config_irq(struct kcs_bmc_device * kcs_bmc,struct platform_device * pdev)145d4e7ac68SAndrew Jeffery static int npcm7xx_kcs_config_irq(struct kcs_bmc_device *kcs_bmc,
1466b2e54f7SHaiyue Wang 				  struct platform_device *pdev)
1476b2e54f7SHaiyue Wang {
1486b2e54f7SHaiyue Wang 	struct device *dev = &pdev->dev;
1496b2e54f7SHaiyue Wang 	int irq;
1506b2e54f7SHaiyue Wang 
1516b2e54f7SHaiyue Wang 	irq = platform_get_irq(pdev, 0);
1526b2e54f7SHaiyue Wang 	if (irq < 0)
1536b2e54f7SHaiyue Wang 		return irq;
1546b2e54f7SHaiyue Wang 
1556b2e54f7SHaiyue Wang 	return devm_request_irq(dev, irq, npcm7xx_kcs_irq, IRQF_SHARED,
1566b2e54f7SHaiyue Wang 				dev_name(dev), kcs_bmc);
1576b2e54f7SHaiyue Wang }
1586b2e54f7SHaiyue Wang 
159faae6e39SAndrew Jeffery static const struct kcs_bmc_device_ops npcm7xx_kcs_ops = {
16028651e6cSAndrew Jeffery 	.irq_mask_update = npcm7xx_kcs_irq_mask_update,
161faae6e39SAndrew Jeffery 	.io_inputb = npcm7xx_kcs_inb,
162faae6e39SAndrew Jeffery 	.io_outputb = npcm7xx_kcs_outb,
163faae6e39SAndrew Jeffery 	.io_updateb = npcm7xx_kcs_updateb,
164faae6e39SAndrew Jeffery };
165faae6e39SAndrew Jeffery 
npcm7xx_kcs_probe(struct platform_device * pdev)1666b2e54f7SHaiyue Wang static int npcm7xx_kcs_probe(struct platform_device *pdev)
1676b2e54f7SHaiyue Wang {
1686b2e54f7SHaiyue Wang 	struct device *dev = &pdev->dev;
1696b2e54f7SHaiyue Wang 	struct npcm7xx_kcs_bmc *priv;
170d4e7ac68SAndrew Jeffery 	struct kcs_bmc_device *kcs_bmc;
1716b2e54f7SHaiyue Wang 	u32 chan;
1726b2e54f7SHaiyue Wang 	int rc;
1736b2e54f7SHaiyue Wang 
1746b2e54f7SHaiyue Wang 	rc = of_property_read_u32(dev->of_node, "kcs_chan", &chan);
1756b2e54f7SHaiyue Wang 	if (rc != 0 || chan == 0 || chan > KCS_CHANNEL_MAX) {
1766b2e54f7SHaiyue Wang 		dev_err(dev, "no valid 'kcs_chan' configured\n");
1776b2e54f7SHaiyue Wang 		return -ENODEV;
1786b2e54f7SHaiyue Wang 	}
1796b2e54f7SHaiyue Wang 
180d7096970SAndrew Jeffery 	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
181d7096970SAndrew Jeffery 	if (!priv)
1826b2e54f7SHaiyue Wang 		return -ENOMEM;
1836b2e54f7SHaiyue Wang 
1846b2e54f7SHaiyue Wang 	priv->map = syscon_node_to_regmap(dev->parent->of_node);
1856b2e54f7SHaiyue Wang 	if (IS_ERR(priv->map)) {
1866b2e54f7SHaiyue Wang 		dev_err(dev, "Couldn't get regmap\n");
1876b2e54f7SHaiyue Wang 		return -ENODEV;
1886b2e54f7SHaiyue Wang 	}
1896b2e54f7SHaiyue Wang 	priv->reg = &npcm7xx_kcs_reg_tbl[chan - 1];
1906b2e54f7SHaiyue Wang 
191d7096970SAndrew Jeffery 	kcs_bmc = &priv->kcs_bmc;
192d7096970SAndrew Jeffery 	kcs_bmc->dev = &pdev->dev;
193d7096970SAndrew Jeffery 	kcs_bmc->channel = chan;
1946b2e54f7SHaiyue Wang 	kcs_bmc->ioreg.idr = priv->reg->dib;
1956b2e54f7SHaiyue Wang 	kcs_bmc->ioreg.odr = priv->reg->dob;
1966b2e54f7SHaiyue Wang 	kcs_bmc->ioreg.str = priv->reg->sts;
197faae6e39SAndrew Jeffery 	kcs_bmc->ops = &npcm7xx_kcs_ops;
1986b2e54f7SHaiyue Wang 
199d7096970SAndrew Jeffery 	platform_set_drvdata(pdev, priv);
2006b2e54f7SHaiyue Wang 
2016b2e54f7SHaiyue Wang 	rc = npcm7xx_kcs_config_irq(kcs_bmc, pdev);
2026b2e54f7SHaiyue Wang 	if (rc)
2036b2e54f7SHaiyue Wang 		return rc;
2046b2e54f7SHaiyue Wang 
205fb6379f5SAndrew Jeffery 	npcm7xx_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0);
20628651e6cSAndrew Jeffery 	npcm7xx_kcs_enable_channel(kcs_bmc, true);
20728651e6cSAndrew Jeffery 
208d7096970SAndrew Jeffery 	rc = kcs_bmc_add_device(kcs_bmc);
2096b2e54f7SHaiyue Wang 	if (rc) {
210d7096970SAndrew Jeffery 		dev_warn(&pdev->dev, "Failed to register channel %d: %d\n", kcs_bmc->channel, rc);
2116b2e54f7SHaiyue Wang 		return rc;
2126b2e54f7SHaiyue Wang 	}
2136b2e54f7SHaiyue Wang 
2146b2e54f7SHaiyue Wang 	pr_info("channel=%u idr=0x%x odr=0x%x str=0x%x\n",
2156b2e54f7SHaiyue Wang 		chan,
2166b2e54f7SHaiyue Wang 		kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr, kcs_bmc->ioreg.str);
2176b2e54f7SHaiyue Wang 
2186b2e54f7SHaiyue Wang 	return 0;
2196b2e54f7SHaiyue Wang }
2206b2e54f7SHaiyue Wang 
npcm7xx_kcs_remove(struct platform_device * pdev)221999dff3cSUwe Kleine-König static void npcm7xx_kcs_remove(struct platform_device *pdev)
2226b2e54f7SHaiyue Wang {
223d7096970SAndrew Jeffery 	struct npcm7xx_kcs_bmc *priv = platform_get_drvdata(pdev);
224d4e7ac68SAndrew Jeffery 	struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc;
2256b2e54f7SHaiyue Wang 
226d7096970SAndrew Jeffery 	kcs_bmc_remove_device(kcs_bmc);
2276b2e54f7SHaiyue Wang 
22828651e6cSAndrew Jeffery 	npcm7xx_kcs_enable_channel(kcs_bmc, false);
22928651e6cSAndrew Jeffery 	npcm7xx_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0);
2306b2e54f7SHaiyue Wang }
2316b2e54f7SHaiyue Wang 
2326b2e54f7SHaiyue Wang static const struct of_device_id npcm_kcs_bmc_match[] = {
2336b2e54f7SHaiyue Wang 	{ .compatible = "nuvoton,npcm750-kcs-bmc" },
2346b2e54f7SHaiyue Wang 	{ }
2356b2e54f7SHaiyue Wang };
2366b2e54f7SHaiyue Wang MODULE_DEVICE_TABLE(of, npcm_kcs_bmc_match);
2376b2e54f7SHaiyue Wang 
2386b2e54f7SHaiyue Wang static struct platform_driver npcm_kcs_bmc_driver = {
2396b2e54f7SHaiyue Wang 	.driver = {
2406b2e54f7SHaiyue Wang 		.name		= DEVICE_NAME,
2416b2e54f7SHaiyue Wang 		.of_match_table	= npcm_kcs_bmc_match,
2426b2e54f7SHaiyue Wang 	},
2436b2e54f7SHaiyue Wang 	.probe	= npcm7xx_kcs_probe,
244*e70140baSLinus Torvalds 	.remove = npcm7xx_kcs_remove,
2456b2e54f7SHaiyue Wang };
2466b2e54f7SHaiyue Wang module_platform_driver(npcm_kcs_bmc_driver);
2476b2e54f7SHaiyue Wang 
2486b2e54f7SHaiyue Wang MODULE_LICENSE("GPL v2");
2496b2e54f7SHaiyue Wang MODULE_AUTHOR("Avi Fishman <avifishman70@gmail.com>");
2506b2e54f7SHaiyue Wang MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
2516b2e54f7SHaiyue Wang MODULE_DESCRIPTION("NPCM7xx device interface to the KCS BMC device");
252