Commit c0c6d209 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'for-linus-5.14-1' of git://github.com/cminyard/linux-ipmi

Pull IPMI driver updates from Corey Minyard:
 "Mostly a restructure of the kcs_bmc driver to make it easier to use
  with different types of devices, and just to clean things up and
  improve things.

  Also some bug fixes for the kcs_bmc driver.

  One fix to the IPMI watchdog to stop the timer when the action is
  none. Not a big deal, but it's the right thing to do"

* tag 'for-linus-5.14-1' of git://github.com/cminyard/linux-ipmi:
  ipmi: kcs_bmc_aspeed: Fix less than zero comparison of a unsigned int
  ipmi: kcs_bmc_aspeed: Optionally apply status address
  ipmi: kcs_bmc_aspeed: Fix IBFIE typo from datasheet
  ipmi: kcs_bmc_aspeed: Implement KCS SerIRQ configuration
  dt-bindings: ipmi: Add optional SerIRQ property to ASPEED KCS devices
  dt-bindings: ipmi: Convert ASPEED KCS binding to schema
  ipmi: kcs_bmc: Add serio adaptor
  ipmi: kcs_bmc: Enable IBF on open
  ipmi: kcs_bmc: Allow clients to control KCS IRQ state
  ipmi: kcs_bmc: Decouple the IPMI chardev from the core
  ipmi: kcs_bmc: Strip private client data from struct kcs_bmc
  ipmi: kcs_bmc: Split headers into device and client
  ipmi: kcs_bmc: Turn the driver data-structures inside-out
  ipmi: kcs_bmc: Split out kcs_bmc_cdev_ipmi
  ipmi: kcs_bmc: Rename {read,write}_{status,data}() functions
  ipmi: kcs_bmc: Make status update atomic
  ipmi: kcs_bmc_aspeed: Use of match data to extract KCS properties
  ipmi/watchdog: Stop watchdog timer when the current action is 'none'
parents 007b350a 5b32dd28
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/ipmi/aspeed,ast2400-kcs-bmc.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: ASPEED BMC KCS Devices
maintainers:
- Andrew Jeffery <andrew@aj.id.au>
description: |
The Aspeed BMC SoCs typically use the Keyboard-Controller-Style (KCS)
interfaces on the LPC bus for in-band IPMI communication with their host.
properties:
compatible:
oneOf:
- description: Channel ID derived from reg
items:
enum:
- aspeed,ast2400-kcs-bmc-v2
- aspeed,ast2500-kcs-bmc-v2
- aspeed,ast2600-kcs-bmc
- description: Old-style with explicit channel ID, no reg
deprecated: true
items:
enum:
- aspeed,ast2400-kcs-bmc
- aspeed,ast2500-kcs-bmc
interrupts:
maxItems: 1
reg:
# maxItems: 3
items:
- description: IDR register
- description: ODR register
- description: STR register
aspeed,lpc-io-reg:
$ref: '/schemas/types.yaml#/definitions/uint32-array'
minItems: 1
maxItems: 2
description: |
The host CPU LPC IO data and status addresses for the device. For most
channels the status address is derived from the data address, but the
status address may be optionally provided.
aspeed,lpc-interrupts:
$ref: "/schemas/types.yaml#/definitions/uint32-array"
minItems: 2
maxItems: 2
description: |
A 2-cell property expressing the LPC SerIRQ number and the interrupt
level/sense encoding (specified in the standard fashion).
Note that the generated interrupt is issued from the BMC to the host, and
thus the target interrupt controller is not captured by the BMC's
devicetree.
kcs_chan:
deprecated: true
$ref: '/schemas/types.yaml#/definitions/uint32'
description: The LPC channel number in the controller
kcs_addr:
deprecated: true
$ref: '/schemas/types.yaml#/definitions/uint32'
description: The host CPU IO map address
required:
- compatible
- interrupts
additionalProperties: false
allOf:
- if:
properties:
compatible:
contains:
enum:
- aspeed,ast2400-kcs-bmc
- aspeed,ast2500-kcs-bmc
then:
required:
- kcs_chan
- kcs_addr
else:
required:
- reg
- aspeed,lpc-io-reg
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
kcs3: kcs@24 {
compatible = "aspeed,ast2600-kcs-bmc";
reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>;
aspeed,lpc-io-reg = <0xca2>;
aspeed,lpc-interrupts = <11 IRQ_TYPE_LEVEL_LOW>;
interrupts = <8>;
};
# Aspeed KCS (Keyboard Controller Style) IPMI interface
The Aspeed SOCs (AST2400 and AST2500) are commonly used as BMCs
(Baseboard Management Controllers) and the KCS interface can be
used to perform in-band IPMI communication with their host.
## v1
Required properties:
- compatible : should be one of
"aspeed,ast2400-kcs-bmc"
"aspeed,ast2500-kcs-bmc"
- interrupts : interrupt generated by the controller
- kcs_chan : The LPC channel number in the controller
- kcs_addr : The host CPU IO map address
## v2
Required properties:
- compatible : should be one of
"aspeed,ast2400-kcs-bmc-v2"
"aspeed,ast2500-kcs-bmc-v2"
- reg : The address and size of the IDR, ODR and STR registers
- interrupts : interrupt generated by the controller
- aspeed,lpc-io-reg : The host CPU LPC IO address for the device
Example:
kcs3: kcs@24 {
compatible = "aspeed,ast2500-kcs-bmc-v2";
reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>;
aspeed,lpc-reg = <0xca2>;
interrupts = <8>;
status = "okay";
};
......@@ -124,6 +124,33 @@ config NPCM7XX_KCS_IPMI_BMC
This support is also available as a module. If so, the module
will be called kcs_bmc_npcm7xx.
config IPMI_KCS_BMC_CDEV_IPMI
depends on IPMI_KCS_BMC
tristate "IPMI character device interface for BMC KCS devices"
help
Provides a BMC-side character device implementing IPMI
semantics for KCS IPMI devices.
Say YES if you wish to expose KCS devices on the BMC for IPMI
purposes.
This support is also available as a module. The module will be
called kcs_bmc_cdev_ipmi.
config IPMI_KCS_BMC_SERIO
depends on IPMI_KCS_BMC && SERIO
tristate "SerIO adaptor for BMC KCS devices"
help
Adapts the BMC KCS device for the SerIO subsystem. This allows users
to take advantage of userspace interfaces provided by SerIO where
appropriate.
Say YES if you wish to expose KCS devices on the BMC via SerIO
interfaces.
This support is also available as a module. The module will be
called kcs_bmc_serio.
config ASPEED_BT_IPMI_BMC
depends on ARCH_ASPEED || COMPILE_TEST
depends on REGMAP && REGMAP_MMIO && MFD_SYSCON
......
......@@ -23,6 +23,8 @@ obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o
obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o
obj-$(CONFIG_IPMI_KCS_BMC_SERIO) += kcs_bmc_serio.o
obj-$(CONFIG_IPMI_KCS_BMC_CDEV_IPMI) += kcs_bmc_cdev_ipmi.o
obj-$(CONFIG_ASPEED_BT_IPMI_BMC) += bt-bmc.o
obj-$(CONFIG_ASPEED_KCS_IPMI_BMC) += kcs_bmc_aspeed.o
obj-$(CONFIG_NPCM7XX_KCS_IPMI_BMC) += kcs_bmc_npcm7xx.o
......
......@@ -371,17 +371,19 @@ static int __ipmi_set_timeout(struct ipmi_smi_msg *smi_msg,
data[0] = 0;
WDOG_SET_TIMER_USE(data[0], WDOG_TIMER_USE_SMS_OS);
if ((ipmi_version_major > 1)
|| ((ipmi_version_major == 1) && (ipmi_version_minor >= 5))) {
if (ipmi_watchdog_state != WDOG_TIMEOUT_NONE) {
if ((ipmi_version_major > 1) ||
((ipmi_version_major == 1) && (ipmi_version_minor >= 5))) {
/* This is an IPMI 1.5-only feature. */
data[0] |= WDOG_DONT_STOP_ON_SET;
} else if (ipmi_watchdog_state != WDOG_TIMEOUT_NONE) {
} else {
/*
* In ipmi 1.0, setting the timer stops the watchdog, we
* need to start it back up again.
*/
hbnow = 1;
}
}
data[1] = 0;
WDOG_SET_TIMEOUT_ACT(data[1], ipmi_watchdog_state);
......
This diff is collapsed.
......@@ -6,54 +6,14 @@
#ifndef __KCS_BMC_H__
#define __KCS_BMC_H__
#include <linux/miscdevice.h>
#include <linux/list.h>
/* Different phases of the KCS BMC module.
* KCS_PHASE_IDLE:
* BMC should not be expecting nor sending any data.
* KCS_PHASE_WRITE_START:
* BMC is receiving a WRITE_START command from system software.
* KCS_PHASE_WRITE_DATA:
* BMC is receiving a data byte from system software.
* KCS_PHASE_WRITE_END_CMD:
* BMC is waiting a last data byte from system software.
* KCS_PHASE_WRITE_DONE:
* BMC has received the whole request from system software.
* KCS_PHASE_WAIT_READ:
* BMC is waiting the response from the upper IPMI service.
* KCS_PHASE_READ:
* BMC is transferring the response to system software.
* KCS_PHASE_ABORT_ERROR1:
* BMC is waiting error status request from system software.
* KCS_PHASE_ABORT_ERROR2:
* BMC is waiting for idle status afer error from system software.
* KCS_PHASE_ERROR:
* BMC has detected a protocol violation at the interface level.
*/
enum kcs_phases {
KCS_PHASE_IDLE,
KCS_PHASE_WRITE_START,
KCS_PHASE_WRITE_DATA,
KCS_PHASE_WRITE_END_CMD,
KCS_PHASE_WRITE_DONE,
#define KCS_BMC_EVENT_TYPE_OBE BIT(0)
#define KCS_BMC_EVENT_TYPE_IBF BIT(1)
KCS_PHASE_WAIT_READ,
KCS_PHASE_READ,
KCS_PHASE_ABORT_ERROR1,
KCS_PHASE_ABORT_ERROR2,
KCS_PHASE_ERROR
};
/* IPMI 2.0 - Table 9-4, KCS Interface Status Codes */
enum kcs_errors {
KCS_NO_ERROR = 0x00,
KCS_ABORTED_BY_COMMAND = 0x01,
KCS_ILLEGAL_CONTROL_CODE = 0x02,
KCS_LENGTH_ERROR = 0x06,
KCS_UNSPECIFIED_ERROR = 0xFF
};
#define KCS_BMC_STR_OBF BIT(0)
#define KCS_BMC_STR_IBF BIT(1)
#define KCS_BMC_STR_CMD_DAT BIT(3)
/* IPMI 2.0 - 9.5, KCS Interface Registers
* @idr: Input Data Register
......@@ -66,43 +26,21 @@ struct kcs_ioreg {
u32 str;
};
struct kcs_bmc {
spinlock_t lock;
struct kcs_bmc_device_ops;
struct kcs_bmc_client;
struct kcs_bmc_device {
struct list_head entry;
struct device *dev;
u32 channel;
int running;
/* Setup by BMC KCS controller driver */
struct kcs_ioreg ioreg;
u8 (*io_inputb)(struct kcs_bmc *kcs_bmc, u32 reg);
void (*io_outputb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 b);
enum kcs_phases phase;
enum kcs_errors error;
wait_queue_head_t queue;
bool data_in_avail;
int data_in_idx;
u8 *data_in;
int data_out_idx;
int data_out_len;
u8 *data_out;
struct mutex mutex;
u8 *kbuffer;
const struct kcs_bmc_device_ops *ops;
struct miscdevice miscdev;
unsigned long priv[];
spinlock_t lock;
struct kcs_bmc_client *client;
};
static inline void *kcs_bmc_priv(struct kcs_bmc *kcs_bmc)
{
return kcs_bmc->priv;
}
int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv,
u32 channel);
#endif /* __KCS_BMC_H__ */
This diff is collapsed.
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2021, IBM Corp. */
#ifndef __KCS_BMC_CONSUMER_H__
#define __KCS_BMC_CONSUMER_H__
#include <linux/irqreturn.h>
#include "kcs_bmc.h"
struct kcs_bmc_driver_ops {
int (*add_device)(struct kcs_bmc_device *kcs_bmc);
int (*remove_device)(struct kcs_bmc_device *kcs_bmc);
};
struct kcs_bmc_driver {
struct list_head entry;
const struct kcs_bmc_driver_ops *ops;
};
struct kcs_bmc_client_ops {
irqreturn_t (*event)(struct kcs_bmc_client *client);
};
struct kcs_bmc_client {
const struct kcs_bmc_client_ops *ops;
struct kcs_bmc_device *dev;
};
void kcs_bmc_register_driver(struct kcs_bmc_driver *drv);
void kcs_bmc_unregister_driver(struct kcs_bmc_driver *drv);
int kcs_bmc_enable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client);
void kcs_bmc_disable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client);
void kcs_bmc_update_event_mask(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 events);
u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc);
void kcs_bmc_write_data(struct kcs_bmc_device *kcs_bmc, u8 data);
u8 kcs_bmc_read_status(struct kcs_bmc_device *kcs_bmc);
void kcs_bmc_write_status(struct kcs_bmc_device *kcs_bmc, u8 data);
void kcs_bmc_update_status(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 val);
#endif
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2021, IBM Corp. */
#ifndef __KCS_BMC_DEVICE_H__
#define __KCS_BMC_DEVICE_H__
#include <linux/irqreturn.h>
#include "kcs_bmc.h"
struct kcs_bmc_device_ops {
void (*irq_mask_update)(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 enable);
u8 (*io_inputb)(struct kcs_bmc_device *kcs_bmc, u32 reg);
void (*io_outputb)(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 b);
void (*io_updateb)(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask, u8 b);
};
irqreturn_t kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc);
int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc);
void kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc);
#endif
......@@ -17,7 +17,7 @@
#include <linux/regmap.h>
#include <linux/slab.h>
#include "kcs_bmc.h"
#include "kcs_bmc_device.h"
#define DEVICE_NAME "npcm-kcs-bmc"
#define KCS_CHANNEL_MAX 3
......@@ -38,6 +38,7 @@
#define KCS2CTL 0x2A
#define KCS3CTL 0x3C
#define KCS_CTL_IBFIE BIT(0)
#define KCS_CTL_OBEIE BIT(1)
#define KCS1IE 0x1C
#define KCS2IE 0x2E
......@@ -65,6 +66,8 @@ struct npcm7xx_kcs_reg {
};
struct npcm7xx_kcs_bmc {
struct kcs_bmc_device kcs_bmc;
struct regmap *map;
const struct npcm7xx_kcs_reg *reg;
......@@ -76,9 +79,14 @@ static const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = {
{ .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE },
};
static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
static inline struct npcm7xx_kcs_bmc *to_npcm7xx_kcs_bmc(struct kcs_bmc_device *kcs_bmc)
{
return container_of(kcs_bmc, struct npcm7xx_kcs_bmc, kcs_bmc);
}
static u8 npcm7xx_kcs_inb(struct kcs_bmc_device *kcs_bmc, u32 reg)
{
struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
u32 val = 0;
int rc;
......@@ -88,37 +96,53 @@ static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
return rc == 0 ? (u8)val : 0;
}
static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
static void npcm7xx_kcs_outb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 data)
{
struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
int rc;
rc = regmap_write(priv->map, reg, data);
WARN(rc != 0, "regmap_write() failed: %d\n", rc);
}
static void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
static void npcm7xx_kcs_updateb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask, u8 data)
{
struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
int rc;
regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
enable ? KCS_CTL_IBFIE : 0);
rc = regmap_update_bits(priv->map, reg, mask, data);
WARN(rc != 0, "regmap_update_bits() failed: %d\n", rc);
}
static void npcm7xx_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enable)
{
struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
regmap_update_bits(priv->map, priv->reg->ie, KCS_IE_IRQE | KCS_IE_HIRQE,
enable ? KCS_IE_IRQE | KCS_IE_HIRQE : 0);
}
static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
static void npcm7xx_kcs_irq_mask_update(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 state)
{
struct kcs_bmc *kcs_bmc = arg;
struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
if (mask & KCS_BMC_EVENT_TYPE_OBE)
regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_OBEIE,
!!(state & KCS_BMC_EVENT_TYPE_OBE) * KCS_CTL_OBEIE);
if (!kcs_bmc_handle_event(kcs_bmc))
return IRQ_HANDLED;
if (mask & KCS_BMC_EVENT_TYPE_IBF)
regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
!!(state & KCS_BMC_EVENT_TYPE_IBF) * KCS_CTL_IBFIE);
}
static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
{
struct kcs_bmc_device *kcs_bmc = arg;
return IRQ_NONE;
return kcs_bmc_handle_event(kcs_bmc);
}
static int npcm7xx_kcs_config_irq(struct kcs_bmc *kcs_bmc,
static int npcm7xx_kcs_config_irq(struct kcs_bmc_device *kcs_bmc,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
......@@ -132,11 +156,18 @@ static int npcm7xx_kcs_config_irq(struct kcs_bmc *kcs_bmc,
dev_name(dev), kcs_bmc);
}
static const struct kcs_bmc_device_ops npcm7xx_kcs_ops = {
.irq_mask_update = npcm7xx_kcs_irq_mask_update,
.io_inputb = npcm7xx_kcs_inb,
.io_outputb = npcm7xx_kcs_outb,
.io_updateb = npcm7xx_kcs_updateb,
};
static int npcm7xx_kcs_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct npcm7xx_kcs_bmc *priv;
struct kcs_bmc *kcs_bmc;
struct kcs_bmc_device *kcs_bmc;
u32 chan;
int rc;
......@@ -146,11 +177,10 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
return -ENODEV;
}
kcs_bmc = kcs_bmc_alloc(dev, sizeof(*priv), chan);
if (!kcs_bmc)
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv = kcs_bmc_priv(kcs_bmc);
priv->map = syscon_node_to_regmap(dev->parent->of_node);
if (IS_ERR(priv->map)) {
dev_err(dev, "Couldn't get regmap\n");
......@@ -158,22 +188,26 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
}
priv->reg = &npcm7xx_kcs_reg_tbl[chan - 1];
kcs_bmc = &priv->kcs_bmc;
kcs_bmc->dev = &pdev->dev;
kcs_bmc->channel = chan;
kcs_bmc->ioreg.idr = priv->reg->dib;
kcs_bmc->ioreg.odr = priv->reg->dob;
kcs_bmc->ioreg.str = priv->reg->sts;
kcs_bmc->io_inputb = npcm7xx_kcs_inb;
kcs_bmc->io_outputb = npcm7xx_kcs_outb;
kcs_bmc->ops = &npcm7xx_kcs_ops;
dev_set_drvdata(dev, kcs_bmc);
platform_set_drvdata(pdev, priv);
npcm7xx_kcs_enable_channel(kcs_bmc, true);
rc = npcm7xx_kcs_config_irq(kcs_bmc, pdev);
if (rc)
return rc;
rc = misc_register(&kcs_bmc->miscdev);
npcm7xx_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0);
npcm7xx_kcs_enable_channel(kcs_bmc, true);
rc = kcs_bmc_add_device(kcs_bmc);
if (rc) {
dev_err(dev, "Unable to register device\n");
dev_warn(&pdev->dev, "Failed to register channel %d: %d\n", kcs_bmc->channel, rc);
return rc;
}
......@@ -186,9 +220,13 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
static int npcm7xx_kcs_remove(struct platform_device *pdev)
{
struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
struct npcm7xx_kcs_bmc *priv = platform_get_drvdata(pdev);
struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc;
kcs_bmc_remove_device(kcs_bmc);
misc_deregister(&kcs_bmc->miscdev);
npcm7xx_kcs_enable_channel(kcs_bmc, false);
npcm7xx_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0);
return 0;
}
......
// SPDX-License-Identifier: GPL-2.0-or-later
/* Copyright (c) 2021 IBM Corp. */
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/sched/signal.h>
#include <linux/serio.h>
#include <linux/slab.h>
#include "kcs_bmc_client.h"
struct kcs_bmc_serio {
struct list_head entry;
struct kcs_bmc_client client;
struct serio *port;
spinlock_t lock;
};
static inline struct kcs_bmc_serio *client_to_kcs_bmc_serio(struct kcs_bmc_client *client)
{
return container_of(client, struct kcs_bmc_serio, client);
}
static irqreturn_t kcs_bmc_serio_event(struct kcs_bmc_client *client)
{
struct kcs_bmc_serio *priv;
u8 handled = IRQ_NONE;
u8 status;
priv = client_to_kcs_bmc_serio(client);
spin_lock(&priv->lock);
status = kcs_bmc_read_status(client->dev);
if (status & KCS_BMC_STR_IBF)
handled = serio_interrupt(priv->port, kcs_bmc_read_data(client->dev), 0);
spin_unlock(&priv->lock);
return handled;
}
static const struct kcs_bmc_client_ops kcs_bmc_serio_client_ops = {
.event = kcs_bmc_serio_event,
};
static int kcs_bmc_serio_open(struct serio *port)
{
struct kcs_bmc_serio *priv = port->port_data;
return kcs_bmc_enable_device(priv->client.dev, &priv->client);
}
static void kcs_bmc_serio_close(struct serio *port)
{
struct kcs_bmc_serio *priv = port->port_data;
kcs_bmc_disable_device(priv->client.dev, &priv->client);
}
static DEFINE_SPINLOCK(kcs_bmc_serio_instances_lock);
static LIST_HEAD(kcs_bmc_serio_instances);
static int kcs_bmc_serio_add_device(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_serio *priv;
struct serio *port;
priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
/* Use kzalloc() as the allocation is cleaned up with kfree() via serio_unregister_port() */
port = kzalloc(sizeof(*port), GFP_KERNEL);
if (!(priv && port))
return -ENOMEM;
port->id.type = SERIO_8042;
port->open = kcs_bmc_serio_open;
port->close = kcs_bmc_serio_close;
port->port_data = priv;
port->dev.parent = kcs_bmc->dev;
spin_lock_init(&priv->lock);
priv->port = port;
priv->client.dev = kcs_bmc;
priv->client.ops = &kcs_bmc_serio_client_ops;
spin_lock_irq(&kcs_bmc_serio_instances_lock);
list_add(&priv->entry, &kcs_bmc_serio_instances);
spin_unlock_irq(&kcs_bmc_serio_instances_lock);
serio_register_port(port);
dev_info(kcs_bmc->dev, "Initialised serio client for channel %d", kcs_bmc->channel);
return 0;
}
static int kcs_bmc_serio_remove_device(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_serio *priv = NULL, *pos;
spin_lock_irq(&kcs_bmc_serio_instances_lock);
list_for_each_entry(pos, &kcs_bmc_serio_instances, entry) {
if (pos->client.dev == kcs_bmc) {
priv = pos;
list_del(&pos->entry);
break;
}
}
spin_unlock_irq(&kcs_bmc_serio_instances_lock);
if (!priv)
return -ENODEV;
/* kfree()s priv->port via put_device() */
serio_unregister_port(priv->port);
/* Ensure the IBF IRQ is disabled if we were the active client */
kcs_bmc_disable_device(kcs_bmc, &priv->client);
devm_kfree(priv->client.dev->dev, priv);
return 0;
}
static const struct kcs_bmc_driver_ops kcs_bmc_serio_driver_ops = {
.add_device = kcs_bmc_serio_add_device,
.remove_device = kcs_bmc_serio_remove_device,
};
static struct kcs_bmc_driver kcs_bmc_serio_driver = {
.ops = &kcs_bmc_serio_driver_ops,
};
static int kcs_bmc_serio_init(void)
{
kcs_bmc_register_driver(&kcs_bmc_serio_driver);
return 0;
}
module_init(kcs_bmc_serio_init);
static void kcs_bmc_serio_exit(void)
{
kcs_bmc_unregister_driver(&kcs_bmc_serio_driver);
}
module_exit(kcs_bmc_serio_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
MODULE_DESCRIPTION("Adapter driver for serio access to BMC KCS devices");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment