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