Pull for IPMI driver for 4.14

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.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEE/Q1c5nzg9ZpmiCaGYfOMkJGb/4EFAmDaXI4ACgkQYfOMkJGb
 /4G8rA//R5blYnkb462OTUn5UNrJq+c/AjsWaTSOLk83liWzGy09xR543CdMlUTw
 eIT0Y9T4ozQt39/v/whISA2AfbRyZtIYukX03zg+MDlke+E1mTArDhiiY7QqvPdK
 OhzVrvtf2DGhAT/WDaL1z410SGzpfyJBO/TXGcWMdoUDWL4bk1XniJF1yuKOH8MW
 tLwiPusC87QCq9wfS2yDa3Ynz5cbmKgMVrNE2+arJveow0G8Ven9HJohAly0hoQG
 wyDBz83jor+u1uTEOQTSMqcsi70K06/acn26mJkr7IM63QO7AwaQxRtvSsi9GQXQ
 C6h77cuCOCrYbESrHP3pM3GIlz2bXc3YlqW0b8HRFaIPaAYP50MLaGrciQF0wLSc
 NSYsY3OtZIu+7XzoRR0Szg8HmnyCPDVzhQwN421hsCfY8OLMoucbE1rOYJJUxo+h
 Zcg2Ok+5121cigAYkRrTtms9l9MR7+MGgCdWgX/l8C+mpI1O3/TSOm5ohH54X+/1
 AqnPWZcH+ayhtayMP9K4HreDcuLZ+R5yHM6fkfb7JWuI2l9JYF5dnpy78+7LUK9h
 iFMJgEKvojVeGun1NSnEJ4i4HxjJsWT2n1upjxABQvHPRvyWDxJD9siWvKeqhCik
 JC7v2F2oYN0MWU4h4Q2XqTj7DD3k8qHaw7CbFvIde6g8zuUtw9w=
 =s87d
 -----END PGP SIGNATURE-----

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'
This commit is contained in:
Linus Torvalds 2021-06-30 11:09:37 -07:00
commit c0c6d209b6
13 changed files with 1626 additions and 734 deletions

View File

@ -0,0 +1,106 @@
# 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>;
};

View File

@ -1,33 +0,0 @@
# 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";
};

View File

@ -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

View File

@ -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

View File

@ -371,16 +371,18 @@ 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))) {
/* This is an IPMI 1.5-only feature. */
data[0] |= WDOG_DONT_STOP_ON_SET;
} else if (ipmi_watchdog_state != WDOG_TIMEOUT_NONE) {
/*
* In ipmi 1.0, setting the timer stops the watchdog, we
* need to start it back up again.
*/
hbnow = 1;
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 {
/*
* In ipmi 1.0, setting the timer stops the watchdog, we
* need to start it back up again.
*/
hbnow = 1;
}
}
data[1] = 0;

View File

@ -1,458 +1,189 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015-2018, Intel Corporation.
* Copyright (c) 2021, IBM Corp.
*/
#define pr_fmt(fmt) "kcs-bmc: " fmt
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/ipmi_bmc.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include "kcs_bmc.h"
#define DEVICE_NAME "ipmi-kcs"
/* Implement both the device and client interfaces here */
#include "kcs_bmc_device.h"
#include "kcs_bmc_client.h"
#define KCS_MSG_BUFSIZ 1000
/* Record registered devices and drivers */
static DEFINE_MUTEX(kcs_bmc_lock);
static LIST_HEAD(kcs_bmc_devices);
static LIST_HEAD(kcs_bmc_drivers);
#define KCS_ZERO_DATA 0
/* Consumer data access */
/* IPMI 2.0 - Table 9-1, KCS Interface Status Register Bits */
#define KCS_STATUS_STATE(state) (state << 6)
#define KCS_STATUS_STATE_MASK GENMASK(7, 6)
#define KCS_STATUS_CMD_DAT BIT(3)
#define KCS_STATUS_SMS_ATN BIT(2)
#define KCS_STATUS_IBF BIT(1)
#define KCS_STATUS_OBF BIT(0)
/* IPMI 2.0 - Table 9-2, KCS Interface State Bits */
enum kcs_states {
IDLE_STATE = 0,
READ_STATE = 1,
WRITE_STATE = 2,
ERROR_STATE = 3,
};
/* IPMI 2.0 - Table 9-3, KCS Interface Control Codes */
#define KCS_CMD_GET_STATUS_ABORT 0x60
#define KCS_CMD_WRITE_START 0x61
#define KCS_CMD_WRITE_END 0x62
#define KCS_CMD_READ_BYTE 0x68
static inline u8 read_data(struct kcs_bmc *kcs_bmc)
u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc)
{
return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
return kcs_bmc->ops->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
}
EXPORT_SYMBOL(kcs_bmc_read_data);
static inline void write_data(struct kcs_bmc *kcs_bmc, u8 data)
void kcs_bmc_write_data(struct kcs_bmc_device *kcs_bmc, u8 data)
{
kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data);
kcs_bmc->ops->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data);
}
EXPORT_SYMBOL(kcs_bmc_write_data);
static inline u8 read_status(struct kcs_bmc *kcs_bmc)
u8 kcs_bmc_read_status(struct kcs_bmc_device *kcs_bmc)
{
return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
return kcs_bmc->ops->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
}
EXPORT_SYMBOL(kcs_bmc_read_status);
static inline void write_status(struct kcs_bmc *kcs_bmc, u8 data)
void kcs_bmc_write_status(struct kcs_bmc_device *kcs_bmc, u8 data)
{
kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data);
kcs_bmc->ops->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data);
}
EXPORT_SYMBOL(kcs_bmc_write_status);
static void update_status_bits(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
void kcs_bmc_update_status(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 val)
{
u8 tmp = read_status(kcs_bmc);
tmp &= ~mask;
tmp |= val & mask;
write_status(kcs_bmc, tmp);
kcs_bmc->ops->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
}
EXPORT_SYMBOL(kcs_bmc_update_status);
static inline void set_state(struct kcs_bmc *kcs_bmc, u8 state)
irqreturn_t kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc)
{
update_status_bits(kcs_bmc, KCS_STATUS_STATE_MASK,
KCS_STATUS_STATE(state));
}
struct kcs_bmc_client *client;
irqreturn_t rc = IRQ_NONE;
static void kcs_force_abort(struct kcs_bmc *kcs_bmc)
{
set_state(kcs_bmc, ERROR_STATE);
read_data(kcs_bmc);
write_data(kcs_bmc, KCS_ZERO_DATA);
spin_lock(&kcs_bmc->lock);
client = kcs_bmc->client;
if (client)
rc = client->ops->event(client);
spin_unlock(&kcs_bmc->lock);
kcs_bmc->phase = KCS_PHASE_ERROR;
kcs_bmc->data_in_avail = false;
kcs_bmc->data_in_idx = 0;
}
static void kcs_bmc_handle_data(struct kcs_bmc *kcs_bmc)
{
u8 data;
switch (kcs_bmc->phase) {
case KCS_PHASE_WRITE_START:
kcs_bmc->phase = KCS_PHASE_WRITE_DATA;
fallthrough;
case KCS_PHASE_WRITE_DATA:
if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
set_state(kcs_bmc, WRITE_STATE);
write_data(kcs_bmc, KCS_ZERO_DATA);
kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
read_data(kcs_bmc);
} else {
kcs_force_abort(kcs_bmc);
kcs_bmc->error = KCS_LENGTH_ERROR;
}
break;
case KCS_PHASE_WRITE_END_CMD:
if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
set_state(kcs_bmc, READ_STATE);
kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
read_data(kcs_bmc);
kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
kcs_bmc->data_in_avail = true;
wake_up_interruptible(&kcs_bmc->queue);
} else {
kcs_force_abort(kcs_bmc);
kcs_bmc->error = KCS_LENGTH_ERROR;
}
break;
case KCS_PHASE_READ:
if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
set_state(kcs_bmc, IDLE_STATE);
data = read_data(kcs_bmc);
if (data != KCS_CMD_READ_BYTE) {
set_state(kcs_bmc, ERROR_STATE);
write_data(kcs_bmc, KCS_ZERO_DATA);
break;
}
if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
write_data(kcs_bmc, KCS_ZERO_DATA);
kcs_bmc->phase = KCS_PHASE_IDLE;
break;
}
write_data(kcs_bmc,
kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
break;
case KCS_PHASE_ABORT_ERROR1:
set_state(kcs_bmc, READ_STATE);
read_data(kcs_bmc);
write_data(kcs_bmc, kcs_bmc->error);
kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
break;
case KCS_PHASE_ABORT_ERROR2:
set_state(kcs_bmc, IDLE_STATE);
read_data(kcs_bmc);
write_data(kcs_bmc, KCS_ZERO_DATA);
kcs_bmc->phase = KCS_PHASE_IDLE;
break;
default:
kcs_force_abort(kcs_bmc);
break;
}
}
static void kcs_bmc_handle_cmd(struct kcs_bmc *kcs_bmc)
{
u8 cmd;
set_state(kcs_bmc, WRITE_STATE);
write_data(kcs_bmc, KCS_ZERO_DATA);
cmd = read_data(kcs_bmc);
switch (cmd) {
case KCS_CMD_WRITE_START:
kcs_bmc->phase = KCS_PHASE_WRITE_START;
kcs_bmc->error = KCS_NO_ERROR;
kcs_bmc->data_in_avail = false;
kcs_bmc->data_in_idx = 0;
break;
case KCS_CMD_WRITE_END:
if (kcs_bmc->phase != KCS_PHASE_WRITE_DATA) {
kcs_force_abort(kcs_bmc);
break;
}
kcs_bmc->phase = KCS_PHASE_WRITE_END_CMD;
break;
case KCS_CMD_GET_STATUS_ABORT:
if (kcs_bmc->error == KCS_NO_ERROR)
kcs_bmc->error = KCS_ABORTED_BY_COMMAND;
kcs_bmc->phase = KCS_PHASE_ABORT_ERROR1;
kcs_bmc->data_in_avail = false;
kcs_bmc->data_in_idx = 0;
break;
default:
kcs_force_abort(kcs_bmc);
kcs_bmc->error = KCS_ILLEGAL_CONTROL_CODE;
break;
}
}
int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
{
unsigned long flags;
int ret = -ENODATA;
u8 status;
spin_lock_irqsave(&kcs_bmc->lock, flags);
status = read_status(kcs_bmc);
if (status & KCS_STATUS_IBF) {
if (!kcs_bmc->running)
kcs_force_abort(kcs_bmc);
else if (status & KCS_STATUS_CMD_DAT)
kcs_bmc_handle_cmd(kcs_bmc);
else
kcs_bmc_handle_data(kcs_bmc);
ret = 0;
}
spin_unlock_irqrestore(&kcs_bmc->lock, flags);
return ret;
return rc;
}
EXPORT_SYMBOL(kcs_bmc_handle_event);
static inline struct kcs_bmc *to_kcs_bmc(struct file *filp)
int kcs_bmc_enable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client)
{
return container_of(filp->private_data, struct kcs_bmc, miscdev);
}
static int kcs_bmc_open(struct inode *inode, struct file *filp)
{
struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
int ret = 0;
int rc;
spin_lock_irq(&kcs_bmc->lock);
if (!kcs_bmc->running)
kcs_bmc->running = 1;
else
ret = -EBUSY;
spin_unlock_irq(&kcs_bmc->lock);
return ret;
}
static __poll_t kcs_bmc_poll(struct file *filp, poll_table *wait)
{
struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
__poll_t mask = 0;
poll_wait(filp, &kcs_bmc->queue, wait);
spin_lock_irq(&kcs_bmc->lock);
if (kcs_bmc->data_in_avail)
mask |= EPOLLIN;
spin_unlock_irq(&kcs_bmc->lock);
return mask;
}
static ssize_t kcs_bmc_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
bool data_avail;
size_t data_len;
ssize_t ret;
if (!(filp->f_flags & O_NONBLOCK))
wait_event_interruptible(kcs_bmc->queue,
kcs_bmc->data_in_avail);
mutex_lock(&kcs_bmc->mutex);
spin_lock_irq(&kcs_bmc->lock);
data_avail = kcs_bmc->data_in_avail;
if (data_avail) {
data_len = kcs_bmc->data_in_idx;
memcpy(kcs_bmc->kbuffer, kcs_bmc->data_in, data_len);
}
spin_unlock_irq(&kcs_bmc->lock);
if (!data_avail) {
ret = -EAGAIN;
goto out_unlock;
}
if (count < data_len) {
pr_err("channel=%u with too large data : %zu\n",
kcs_bmc->channel, data_len);
spin_lock_irq(&kcs_bmc->lock);
kcs_force_abort(kcs_bmc);
spin_unlock_irq(&kcs_bmc->lock);
ret = -EOVERFLOW;
goto out_unlock;
}
if (copy_to_user(buf, kcs_bmc->kbuffer, data_len)) {
ret = -EFAULT;
goto out_unlock;
}
ret = data_len;
spin_lock_irq(&kcs_bmc->lock);
if (kcs_bmc->phase == KCS_PHASE_WRITE_DONE) {
kcs_bmc->phase = KCS_PHASE_WAIT_READ;
kcs_bmc->data_in_avail = false;
kcs_bmc->data_in_idx = 0;
if (kcs_bmc->client) {
rc = -EBUSY;
} else {
ret = -EAGAIN;
u8 mask = KCS_BMC_EVENT_TYPE_IBF;
kcs_bmc->client = client;
kcs_bmc_update_event_mask(kcs_bmc, mask, mask);
rc = 0;
}
spin_unlock_irq(&kcs_bmc->lock);
out_unlock:
mutex_unlock(&kcs_bmc->mutex);
return ret;
return rc;
}
EXPORT_SYMBOL(kcs_bmc_enable_device);
static ssize_t kcs_bmc_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
void kcs_bmc_disable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client)
{
struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
ssize_t ret;
/* a minimum response size '3' : netfn + cmd + ccode */
if (count < 3 || count > KCS_MSG_BUFSIZ)
return -EINVAL;
mutex_lock(&kcs_bmc->mutex);
if (copy_from_user(kcs_bmc->kbuffer, buf, count)) {
ret = -EFAULT;
goto out_unlock;
}
spin_lock_irq(&kcs_bmc->lock);
if (kcs_bmc->phase == KCS_PHASE_WAIT_READ) {
kcs_bmc->phase = KCS_PHASE_READ;
kcs_bmc->data_out_idx = 1;
kcs_bmc->data_out_len = count;
memcpy(kcs_bmc->data_out, kcs_bmc->kbuffer, count);
write_data(kcs_bmc, kcs_bmc->data_out[0]);
ret = count;
} else {
ret = -EINVAL;
if (client == kcs_bmc->client) {
u8 mask = KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE;
kcs_bmc_update_event_mask(kcs_bmc, mask, 0);
kcs_bmc->client = NULL;
}
spin_unlock_irq(&kcs_bmc->lock);
out_unlock:
mutex_unlock(&kcs_bmc->mutex);
return ret;
}
EXPORT_SYMBOL(kcs_bmc_disable_device);
static long kcs_bmc_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
long ret = 0;
spin_lock_irq(&kcs_bmc->lock);
switch (cmd) {
case IPMI_BMC_IOCTL_SET_SMS_ATN:
update_status_bits(kcs_bmc, KCS_STATUS_SMS_ATN,
KCS_STATUS_SMS_ATN);
break;
case IPMI_BMC_IOCTL_CLEAR_SMS_ATN:
update_status_bits(kcs_bmc, KCS_STATUS_SMS_ATN,
0);
break;
case IPMI_BMC_IOCTL_FORCE_ABORT:
kcs_force_abort(kcs_bmc);
break;
default:
ret = -EINVAL;
break;
}
spin_unlock_irq(&kcs_bmc->lock);
return ret;
}
static int kcs_bmc_release(struct inode *inode, struct file *filp)
{
struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
spin_lock_irq(&kcs_bmc->lock);
kcs_bmc->running = 0;
kcs_force_abort(kcs_bmc);
spin_unlock_irq(&kcs_bmc->lock);
return 0;
}
static const struct file_operations kcs_bmc_fops = {
.owner = THIS_MODULE,
.open = kcs_bmc_open,
.read = kcs_bmc_read,
.write = kcs_bmc_write,
.release = kcs_bmc_release,
.poll = kcs_bmc_poll,
.unlocked_ioctl = kcs_bmc_ioctl,
};
struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel)
{
struct kcs_bmc *kcs_bmc;
kcs_bmc = devm_kzalloc(dev, sizeof(*kcs_bmc) + sizeof_priv, GFP_KERNEL);
if (!kcs_bmc)
return NULL;
struct kcs_bmc_driver *drv;
int error = 0;
int rc;
spin_lock_init(&kcs_bmc->lock);
kcs_bmc->channel = channel;
kcs_bmc->client = NULL;
mutex_init(&kcs_bmc->mutex);
init_waitqueue_head(&kcs_bmc->queue);
mutex_lock(&kcs_bmc_lock);
list_add(&kcs_bmc->entry, &kcs_bmc_devices);
list_for_each_entry(drv, &kcs_bmc_drivers, entry) {
rc = drv->ops->add_device(kcs_bmc);
if (!rc)
continue;
kcs_bmc->data_in = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
kcs_bmc->data_out = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
kcs_bmc->kbuffer = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
kcs_bmc->channel, rc);
error = rc;
}
mutex_unlock(&kcs_bmc_lock);
kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
kcs_bmc->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%u",
DEVICE_NAME, channel);
if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
!kcs_bmc->miscdev.name)
return NULL;
kcs_bmc->miscdev.fops = &kcs_bmc_fops;
return kcs_bmc;
return error;
}
EXPORT_SYMBOL(kcs_bmc_alloc);
EXPORT_SYMBOL(kcs_bmc_add_device);
void kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_driver *drv;
int rc;
mutex_lock(&kcs_bmc_lock);
list_del(&kcs_bmc->entry);
list_for_each_entry(drv, &kcs_bmc_drivers, entry) {
rc = drv->ops->remove_device(kcs_bmc);
if (rc)
dev_err(kcs_bmc->dev, "Failed to remove chardev for KCS channel %d: %d",
kcs_bmc->channel, rc);
}
mutex_unlock(&kcs_bmc_lock);
}
EXPORT_SYMBOL(kcs_bmc_remove_device);
void kcs_bmc_register_driver(struct kcs_bmc_driver *drv)
{
struct kcs_bmc_device *kcs_bmc;
int rc;
mutex_lock(&kcs_bmc_lock);
list_add(&drv->entry, &kcs_bmc_drivers);
list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
rc = drv->ops->add_device(kcs_bmc);
if (rc)
dev_err(kcs_bmc->dev, "Failed to add driver for KCS channel %d: %d",
kcs_bmc->channel, rc);
}
mutex_unlock(&kcs_bmc_lock);
}
EXPORT_SYMBOL(kcs_bmc_register_driver);
void kcs_bmc_unregister_driver(struct kcs_bmc_driver *drv)
{
struct kcs_bmc_device *kcs_bmc;
int rc;
mutex_lock(&kcs_bmc_lock);
list_del(&drv->entry);
list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
rc = drv->ops->remove_device(kcs_bmc);
if (rc)
dev_err(kcs_bmc->dev, "Failed to remove driver for KCS channel %d: %d",
kcs_bmc->channel, rc);
}
mutex_unlock(&kcs_bmc_lock);
}
EXPORT_SYMBOL(kcs_bmc_unregister_driver);
void kcs_bmc_update_event_mask(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 events)
{
kcs_bmc->ops->irq_mask_update(kcs_bmc, mask, events);
}
EXPORT_SYMBOL(kcs_bmc_update_event_mask);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");

View File

@ -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,
#define KCS_BMC_EVENT_TYPE_OBE BIT(0)
#define KCS_BMC_EVENT_TYPE_IBF BIT(1)
KCS_PHASE_WRITE_START,
KCS_PHASE_WRITE_DATA,
KCS_PHASE_WRITE_END_CMD,
KCS_PHASE_WRITE_DONE,
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;
const struct kcs_bmc_device_ops *ops;
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;
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__ */

View File

@ -9,10 +9,12 @@
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/regmap.h>
@ -20,24 +22,53 @@
#include <linux/slab.h>
#include <linux/timer.h>
#include "kcs_bmc.h"
#include "kcs_bmc_device.h"
#define DEVICE_NAME "ast-kcs-bmc"
#define KCS_CHANNEL_MAX 4
/*
* Field class descriptions
*
* LPCyE Enable LPC channel y
* IBFIEy Input Buffer Full IRQ Enable for LPC channel y
* IRQxEy Assert SerIRQ x for LPC channel y (Deprecated, use IDyIRQX, IRQXEy)
* IDyIRQX Use the specified 4-bit SerIRQ for LPC channel y
* SELyIRQX SerIRQ polarity for LPC channel y (low: 0, high: 1)
* IRQXEy Assert the SerIRQ specified in IDyIRQX for LPC channel y
*/
#define LPC_TYIRQX_LOW 0b00
#define LPC_TYIRQX_HIGH 0b01
#define LPC_TYIRQX_RSVD 0b10
#define LPC_TYIRQX_RISING 0b11
#define LPC_HICR0 0x000
#define LPC_HICR0_LPC3E BIT(7)
#define LPC_HICR0_LPC2E BIT(6)
#define LPC_HICR0_LPC1E BIT(5)
#define LPC_HICR2 0x008
#define LPC_HICR2_IBFIF3 BIT(3)
#define LPC_HICR2_IBFIF2 BIT(2)
#define LPC_HICR2_IBFIF1 BIT(1)
#define LPC_HICR2_IBFIE3 BIT(3)
#define LPC_HICR2_IBFIE2 BIT(2)
#define LPC_HICR2_IBFIE1 BIT(1)
#define LPC_HICR4 0x010
#define LPC_HICR4_LADR12AS BIT(7)
#define LPC_HICR4_KCSENBL BIT(2)
#define LPC_SIRQCR0 0x070
/* IRQ{12,1}E1 are deprecated as of AST2600 A3 but necessary for prior chips */
#define LPC_SIRQCR0_IRQ12E1 BIT(1)
#define LPC_SIRQCR0_IRQ1E1 BIT(0)
#define LPC_HICR5 0x080
#define LPC_HICR5_ID3IRQX_MASK GENMASK(23, 20)
#define LPC_HICR5_ID3IRQX_SHIFT 20
#define LPC_HICR5_ID2IRQX_MASK GENMASK(19, 16)
#define LPC_HICR5_ID2IRQX_SHIFT 16
#define LPC_HICR5_SEL3IRQX BIT(15)
#define LPC_HICR5_IRQXE3 BIT(14)
#define LPC_HICR5_SEL2IRQX BIT(13)
#define LPC_HICR5_IRQXE2 BIT(12)
#define LPC_LADR3H 0x014
#define LPC_LADR3L 0x018
#define LPC_LADR12H 0x01C
@ -52,21 +83,64 @@
#define LPC_STR2 0x040
#define LPC_STR3 0x044
#define LPC_HICRB 0x100
#define LPC_HICRB_IBFIF4 BIT(1)
#define LPC_HICRB_EN16LADR2 BIT(5)
#define LPC_HICRB_EN16LADR1 BIT(4)
#define LPC_HICRB_IBFIE4 BIT(1)
#define LPC_HICRB_LPC4E BIT(0)
#define LPC_HICRC 0x104
#define LPC_HICRC_ID4IRQX_MASK GENMASK(7, 4)
#define LPC_HICRC_ID4IRQX_SHIFT 4
#define LPC_HICRC_TY4IRQX_MASK GENMASK(3, 2)
#define LPC_HICRC_TY4IRQX_SHIFT 2
#define LPC_HICRC_OBF4_AUTO_CLR BIT(1)
#define LPC_HICRC_IRQXE4 BIT(0)
#define LPC_LADR4 0x110
#define LPC_IDR4 0x114
#define LPC_ODR4 0x118
#define LPC_STR4 0x11C
#define LPC_LSADR12 0x120
#define LPC_LSADR12_LSADR2_MASK GENMASK(31, 16)
#define LPC_LSADR12_LSADR2_SHIFT 16
#define LPC_LSADR12_LSADR1_MASK GENMASK(15, 0)
#define LPC_LSADR12_LSADR1_SHIFT 0
struct aspeed_kcs_bmc {
struct regmap *map;
#define OBE_POLL_PERIOD (HZ / 2)
enum aspeed_kcs_irq_mode {
aspeed_kcs_irq_none,
aspeed_kcs_irq_serirq,
};
struct aspeed_kcs_bmc {
struct kcs_bmc_device kcs_bmc;
static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
struct regmap *map;
struct {
enum aspeed_kcs_irq_mode mode;
int id;
} upstream_irq;
struct {
spinlock_t lock;
bool remove;
struct timer_list timer;
} obe;
};
struct aspeed_kcs_of_ops {
int (*get_channel)(struct platform_device *pdev);
int (*get_io_address)(struct platform_device *pdev, u32 addrs[2]);
};
static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc_device *kcs_bmc)
{
struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
return container_of(kcs_bmc, struct aspeed_kcs_bmc, kcs_bmc);
}
static u8 aspeed_kcs_inb(struct kcs_bmc_device *kcs_bmc, u32 reg)
{
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
u32 val = 0;
int rc;
@ -76,15 +150,66 @@ static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
return rc == 0 ? (u8) val : 0;
}
static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
static void aspeed_kcs_outb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 data)
{
struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
int rc;
rc = regmap_write(priv->map, reg, data);
WARN(rc != 0, "regmap_write() failed: %d\n", rc);
/* Trigger the upstream IRQ on ODR writes, if enabled */
switch (reg) {
case LPC_ODR1:
case LPC_ODR2:
case LPC_ODR3:
case LPC_ODR4:
break;
default:
return;
}
if (priv->upstream_irq.mode != aspeed_kcs_irq_serirq)
return;
switch (kcs_bmc->channel) {
case 1:
switch (priv->upstream_irq.id) {
case 12:
regmap_update_bits(priv->map, LPC_SIRQCR0, LPC_SIRQCR0_IRQ12E1,
LPC_SIRQCR0_IRQ12E1);
break;
case 1:
regmap_update_bits(priv->map, LPC_SIRQCR0, LPC_SIRQCR0_IRQ1E1,
LPC_SIRQCR0_IRQ1E1);
break;
default:
break;
}
break;
case 2:
regmap_update_bits(priv->map, LPC_HICR5, LPC_HICR5_IRQXE2, LPC_HICR5_IRQXE2);
break;
case 3:
regmap_update_bits(priv->map, LPC_HICR5, LPC_HICR5_IRQXE3, LPC_HICR5_IRQXE3);
break;
case 4:
regmap_update_bits(priv->map, LPC_HICRC, LPC_HICRC_IRQXE4, LPC_HICRC_IRQXE4);
break;
default:
break;
}
}
static void aspeed_kcs_updateb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask, u8 val)
{
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
int rc;
rc = regmap_update_bits(priv->map, reg, mask, val);
WARN(rc != 0, "regmap_update_bits() failed: %d\n", rc);
}
/*
* AST_usrGuide_KCS.pdf
@ -99,118 +224,237 @@ static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
* C. KCS4
* D / C : CA4h / CA5h
*/
static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
static int aspeed_kcs_set_address(struct kcs_bmc_device *kcs_bmc, u32 addrs[2], int nr_addrs)
{
struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
switch (kcs_bmc->channel) {
if (WARN_ON(nr_addrs < 1 || nr_addrs > 2))
return -EINVAL;
switch (priv->kcs_bmc.channel) {
case 1:
regmap_update_bits(priv->map, LPC_HICR4,
LPC_HICR4_LADR12AS, 0);
regmap_write(priv->map, LPC_LADR12H, addr >> 8);
regmap_write(priv->map, LPC_LADR12L, addr & 0xFF);
break;
regmap_update_bits(priv->map, LPC_HICR4, LPC_HICR4_LADR12AS, 0);
regmap_write(priv->map, LPC_LADR12H, addrs[0] >> 8);
regmap_write(priv->map, LPC_LADR12L, addrs[0] & 0xFF);
if (nr_addrs == 2) {
regmap_update_bits(priv->map, LPC_LSADR12, LPC_LSADR12_LSADR1_MASK,
addrs[1] << LPC_LSADR12_LSADR1_SHIFT);
case 2:
regmap_update_bits(priv->map, LPC_HICR4,
LPC_HICR4_LADR12AS, LPC_HICR4_LADR12AS);
regmap_write(priv->map, LPC_LADR12H, addr >> 8);
regmap_write(priv->map, LPC_LADR12L, addr & 0xFF);
break;
case 3:
regmap_write(priv->map, LPC_LADR3H, addr >> 8);
regmap_write(priv->map, LPC_LADR3L, addr & 0xFF);
break;
case 4:
regmap_write(priv->map, LPC_LADR4, ((addr + 1) << 16) |
addr);
break;
default:
break;
}
}
static void aspeed_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
{
struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
switch (kcs_bmc->channel) {
case 1:
if (enable) {
regmap_update_bits(priv->map, LPC_HICR2,
LPC_HICR2_IBFIF1, LPC_HICR2_IBFIF1);
regmap_update_bits(priv->map, LPC_HICR0,
LPC_HICR0_LPC1E, LPC_HICR0_LPC1E);
} else {
regmap_update_bits(priv->map, LPC_HICR0,
LPC_HICR0_LPC1E, 0);
regmap_update_bits(priv->map, LPC_HICR2,
LPC_HICR2_IBFIF1, 0);
regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_EN16LADR1,
LPC_HICRB_EN16LADR1);
}
break;
case 2:
if (enable) {
regmap_update_bits(priv->map, LPC_HICR2,
LPC_HICR2_IBFIF2, LPC_HICR2_IBFIF2);
regmap_update_bits(priv->map, LPC_HICR0,
LPC_HICR0_LPC2E, LPC_HICR0_LPC2E);
} else {
regmap_update_bits(priv->map, LPC_HICR0,
LPC_HICR0_LPC2E, 0);
regmap_update_bits(priv->map, LPC_HICR2,
LPC_HICR2_IBFIF2, 0);
regmap_update_bits(priv->map, LPC_HICR4, LPC_HICR4_LADR12AS, LPC_HICR4_LADR12AS);
regmap_write(priv->map, LPC_LADR12H, addrs[0] >> 8);
regmap_write(priv->map, LPC_LADR12L, addrs[0] & 0xFF);
if (nr_addrs == 2) {
regmap_update_bits(priv->map, LPC_LSADR12, LPC_LSADR12_LSADR2_MASK,
addrs[1] << LPC_LSADR12_LSADR2_SHIFT);
regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_EN16LADR2,
LPC_HICRB_EN16LADR2);
}
break;
case 3:
if (enable) {
regmap_update_bits(priv->map, LPC_HICR2,
LPC_HICR2_IBFIF3, LPC_HICR2_IBFIF3);
regmap_update_bits(priv->map, LPC_HICR0,
LPC_HICR0_LPC3E, LPC_HICR0_LPC3E);
regmap_update_bits(priv->map, LPC_HICR4,
LPC_HICR4_KCSENBL, LPC_HICR4_KCSENBL);
} else {
regmap_update_bits(priv->map, LPC_HICR0,
LPC_HICR0_LPC3E, 0);
regmap_update_bits(priv->map, LPC_HICR4,
LPC_HICR4_KCSENBL, 0);
regmap_update_bits(priv->map, LPC_HICR2,
LPC_HICR2_IBFIF3, 0);
if (nr_addrs == 2) {
dev_err(priv->kcs_bmc.dev,
"Channel 3 only supports inferred status IO address\n");
return -EINVAL;
}
regmap_write(priv->map, LPC_LADR3H, addrs[0] >> 8);
regmap_write(priv->map, LPC_LADR3L, addrs[0] & 0xFF);
break;
case 4:
if (enable)
regmap_update_bits(priv->map, LPC_HICRB,
LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E,
LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E);
if (nr_addrs == 1)
regmap_write(priv->map, LPC_LADR4, ((addrs[0] + 1) << 16) | addrs[0]);
else
regmap_update_bits(priv->map, LPC_HICRB,
LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E,
0);
regmap_write(priv->map, LPC_LADR4, (addrs[1] << 16) | addrs[0]);
break;
default:
break;
return -EINVAL;
}
return 0;
}
static inline int aspeed_kcs_map_serirq_type(u32 dt_type)
{
switch (dt_type) {
case IRQ_TYPE_EDGE_RISING:
return LPC_TYIRQX_RISING;
case IRQ_TYPE_LEVEL_HIGH:
return LPC_TYIRQX_HIGH;
case IRQ_TYPE_LEVEL_LOW:
return LPC_TYIRQX_LOW;
default:
return -EINVAL;
}
}
static int aspeed_kcs_config_upstream_irq(struct aspeed_kcs_bmc *priv, u32 id, u32 dt_type)
{
unsigned int mask, val, hw_type;
int ret;
if (id > 15)
return -EINVAL;
ret = aspeed_kcs_map_serirq_type(dt_type);
if (ret < 0)
return ret;
hw_type = ret;
priv->upstream_irq.mode = aspeed_kcs_irq_serirq;
priv->upstream_irq.id = id;
switch (priv->kcs_bmc.channel) {
case 1:
/* Needs IRQxE1 rather than (ID1IRQX, SEL1IRQX, IRQXE1) before AST2600 A3 */
break;
case 2:
if (!(hw_type == LPC_TYIRQX_LOW || hw_type == LPC_TYIRQX_HIGH))
return -EINVAL;
mask = LPC_HICR5_SEL2IRQX | LPC_HICR5_ID2IRQX_MASK;
val = (id << LPC_HICR5_ID2IRQX_SHIFT);
val |= (hw_type == LPC_TYIRQX_HIGH) ? LPC_HICR5_SEL2IRQX : 0;
regmap_update_bits(priv->map, LPC_HICR5, mask, val);
break;
case 3:
if (!(hw_type == LPC_TYIRQX_LOW || hw_type == LPC_TYIRQX_HIGH))
return -EINVAL;
mask = LPC_HICR5_SEL3IRQX | LPC_HICR5_ID3IRQX_MASK;
val = (id << LPC_HICR5_ID3IRQX_SHIFT);
val |= (hw_type == LPC_TYIRQX_HIGH) ? LPC_HICR5_SEL3IRQX : 0;
regmap_update_bits(priv->map, LPC_HICR5, mask, val);
break;
case 4:
mask = LPC_HICRC_ID4IRQX_MASK | LPC_HICRC_TY4IRQX_MASK | LPC_HICRC_OBF4_AUTO_CLR;
val = (id << LPC_HICRC_ID4IRQX_SHIFT) | (hw_type << LPC_HICRC_TY4IRQX_SHIFT);
regmap_update_bits(priv->map, LPC_HICRC, mask, val);
break;
default:
dev_warn(priv->kcs_bmc.dev,
"SerIRQ configuration not supported on KCS channel %d\n",
priv->kcs_bmc.channel);
return -EINVAL;
}
return 0;
}
static void aspeed_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enable)
{
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
switch (kcs_bmc->channel) {
case 1:
regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC1E, enable * LPC_HICR0_LPC1E);
return;
case 2:
regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC2E, enable * LPC_HICR0_LPC2E);
return;
case 3:
regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC3E, enable * LPC_HICR0_LPC3E);
regmap_update_bits(priv->map, LPC_HICR4,
LPC_HICR4_KCSENBL, enable * LPC_HICR4_KCSENBL);
return;
case 4:
regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_LPC4E, enable * LPC_HICRB_LPC4E);
return;
default:
pr_warn("%s: Unsupported channel: %d", __func__, kcs_bmc->channel);
return;
}
}
static void aspeed_kcs_check_obe(struct timer_list *timer)
{
struct aspeed_kcs_bmc *priv = container_of(timer, struct aspeed_kcs_bmc, obe.timer);
unsigned long flags;
u8 str;
spin_lock_irqsave(&priv->obe.lock, flags);
if (priv->obe.remove) {
spin_unlock_irqrestore(&priv->obe.lock, flags);
return;
}
str = aspeed_kcs_inb(&priv->kcs_bmc, priv->kcs_bmc.ioreg.str);
if (str & KCS_BMC_STR_OBF) {
mod_timer(timer, jiffies + OBE_POLL_PERIOD);
spin_unlock_irqrestore(&priv->obe.lock, flags);
return;
}
spin_unlock_irqrestore(&priv->obe.lock, flags);
kcs_bmc_handle_event(&priv->kcs_bmc);
}
static void aspeed_kcs_irq_mask_update(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 state)
{
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
/* We don't have an OBE IRQ, emulate it */
if (mask & KCS_BMC_EVENT_TYPE_OBE) {
if (KCS_BMC_EVENT_TYPE_OBE & state)
mod_timer(&priv->obe.timer, jiffies + OBE_POLL_PERIOD);
else
del_timer(&priv->obe.timer);
}
if (mask & KCS_BMC_EVENT_TYPE_IBF) {
const bool enable = !!(state & KCS_BMC_EVENT_TYPE_IBF);
switch (kcs_bmc->channel) {
case 1:
regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIE1,
enable * LPC_HICR2_IBFIE1);
return;
case 2:
regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIE2,
enable * LPC_HICR2_IBFIE2);
return;
case 3:
regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIE3,
enable * LPC_HICR2_IBFIE3);
return;
case 4:
regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_IBFIE4,
enable * LPC_HICRB_IBFIE4);
return;
default:
pr_warn("%s: Unsupported channel: %d", __func__, kcs_bmc->channel);
return;
}
}
}
static const struct kcs_bmc_device_ops aspeed_kcs_ops = {
.irq_mask_update = aspeed_kcs_irq_mask_update,
.io_inputb = aspeed_kcs_inb,
.io_outputb = aspeed_kcs_outb,
.io_updateb = aspeed_kcs_updateb,
};
static irqreturn_t aspeed_kcs_irq(int irq, void *arg)
{
struct kcs_bmc *kcs_bmc = arg;
struct kcs_bmc_device *kcs_bmc = arg;
if (!kcs_bmc_handle_event(kcs_bmc))
return IRQ_HANDLED;
return IRQ_NONE;
return kcs_bmc_handle_event(kcs_bmc);
}
static int aspeed_kcs_config_irq(struct kcs_bmc *kcs_bmc,
static int aspeed_kcs_config_downstream_irq(struct kcs_bmc_device *kcs_bmc,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@ -231,13 +475,10 @@ static const struct kcs_ioreg ast_kcs_bmc_ioregs[KCS_CHANNEL_MAX] = {
{ .idr = LPC_IDR4, .odr = LPC_ODR4, .str = LPC_STR4 },
};
static struct kcs_bmc *aspeed_kcs_probe_of_v1(struct platform_device *pdev)
static int aspeed_kcs_of_v1_get_channel(struct platform_device *pdev)
{
struct aspeed_kcs_bmc *priv;
struct device_node *np;
struct kcs_bmc *kcs;
u32 channel;
u32 slave;
int rc;
np = pdev->dev.of_node;
@ -245,166 +486,213 @@ static struct kcs_bmc *aspeed_kcs_probe_of_v1(struct platform_device *pdev)
rc = of_property_read_u32(np, "kcs_chan", &channel);
if ((rc != 0) || (channel == 0 || channel > KCS_CHANNEL_MAX)) {
dev_err(&pdev->dev, "no valid 'kcs_chan' configured\n");
return ERR_PTR(-EINVAL);
return -EINVAL;
}
kcs = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
if (!kcs)
return ERR_PTR(-ENOMEM);
return channel;
}
priv = kcs_bmc_priv(kcs);
priv->map = syscon_node_to_regmap(pdev->dev.parent->of_node);
if (IS_ERR(priv->map)) {
dev_err(&pdev->dev, "Couldn't get regmap\n");
return ERR_PTR(-ENODEV);
}
static int
aspeed_kcs_of_v1_get_io_address(struct platform_device *pdev, u32 addrs[2])
{
int rc;
rc = of_property_read_u32(np, "kcs_addr", &slave);
if (rc) {
rc = of_property_read_u32(pdev->dev.of_node, "kcs_addr", addrs);
if (rc || addrs[0] > 0xffff) {
dev_err(&pdev->dev, "no valid 'kcs_addr' configured\n");
return ERR_PTR(-EINVAL);
return -EINVAL;
}
kcs->ioreg = ast_kcs_bmc_ioregs[channel - 1];
aspeed_kcs_set_address(kcs, slave);
return kcs;
return 1;
}
static int aspeed_kcs_calculate_channel(const struct kcs_ioreg *regs)
static int aspeed_kcs_of_v2_get_channel(struct platform_device *pdev)
{
int i;
for (i = 0; i < ARRAY_SIZE(ast_kcs_bmc_ioregs); i++) {
if (!memcmp(&ast_kcs_bmc_ioregs[i], regs, sizeof(*regs)))
return i + 1;
}
return -EINVAL;
}
static struct kcs_bmc *aspeed_kcs_probe_of_v2(struct platform_device *pdev)
{
struct aspeed_kcs_bmc *priv;
struct device_node *np;
struct kcs_ioreg ioreg;
struct kcs_bmc *kcs;
const __be32 *reg;
int channel;
u32 slave;
int rc;
int i;
np = pdev->dev.of_node;
/* Don't translate addresses, we want offsets for the regmaps */
reg = of_get_address(np, 0, NULL, NULL);
if (!reg)
return ERR_PTR(-EINVAL);
return -EINVAL;
ioreg.idr = be32_to_cpup(reg);
reg = of_get_address(np, 1, NULL, NULL);
if (!reg)
return ERR_PTR(-EINVAL);
return -EINVAL;
ioreg.odr = be32_to_cpup(reg);
reg = of_get_address(np, 2, NULL, NULL);
if (!reg)
return ERR_PTR(-EINVAL);
return -EINVAL;
ioreg.str = be32_to_cpup(reg);
channel = aspeed_kcs_calculate_channel(&ioreg);
if (channel < 0)
return ERR_PTR(channel);
kcs = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
if (!kcs)
return ERR_PTR(-ENOMEM);
kcs->ioreg = ioreg;
priv = kcs_bmc_priv(kcs);
priv->map = syscon_node_to_regmap(pdev->dev.parent->of_node);
if (IS_ERR(priv->map)) {
dev_err(&pdev->dev, "Couldn't get regmap\n");
return ERR_PTR(-ENODEV);
for (i = 0; i < ARRAY_SIZE(ast_kcs_bmc_ioregs); i++) {
if (!memcmp(&ast_kcs_bmc_ioregs[i], &ioreg, sizeof(ioreg)))
return i + 1;
}
rc = of_property_read_u32(np, "aspeed,lpc-io-reg", &slave);
if (rc)
return ERR_PTR(rc);
return -EINVAL;
}
aspeed_kcs_set_address(kcs, slave);
static int
aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev, u32 addrs[2])
{
int rc;
return kcs;
rc = of_property_read_variable_u32_array(pdev->dev.of_node,
"aspeed,lpc-io-reg",
addrs, 1, 2);
if (rc < 0) {
dev_err(&pdev->dev, "No valid 'aspeed,lpc-io-reg' configured\n");
return rc;
}
if (addrs[0] > 0xffff) {
dev_err(&pdev->dev, "Invalid data address in 'aspeed,lpc-io-reg'\n");
return -EINVAL;
}
if (rc == 2 && addrs[1] > 0xffff) {
dev_err(&pdev->dev, "Invalid status address in 'aspeed,lpc-io-reg'\n");
return -EINVAL;
}
return rc;
}
static int aspeed_kcs_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct kcs_bmc *kcs_bmc;
const struct aspeed_kcs_of_ops *ops;
struct kcs_bmc_device *kcs_bmc;
struct aspeed_kcs_bmc *priv;
struct device_node *np;
int rc;
bool have_upstream_irq;
u32 upstream_irq[2];
int rc, channel;
int nr_addrs;
u32 addrs[2];
np = dev->of_node->parent;
np = pdev->dev.of_node->parent;
if (!of_device_is_compatible(np, "aspeed,ast2400-lpc-v2") &&
!of_device_is_compatible(np, "aspeed,ast2500-lpc-v2") &&
!of_device_is_compatible(np, "aspeed,ast2600-lpc-v2")) {
dev_err(dev, "unsupported LPC device binding\n");
dev_err(&pdev->dev, "unsupported LPC device binding\n");
return -ENODEV;
}
np = dev->of_node;
if (of_device_is_compatible(np, "aspeed,ast2400-kcs-bmc") ||
of_device_is_compatible(np, "aspeed,ast2500-kcs-bmc"))
kcs_bmc = aspeed_kcs_probe_of_v1(pdev);
else if (of_device_is_compatible(np, "aspeed,ast2400-kcs-bmc-v2") ||
of_device_is_compatible(np, "aspeed,ast2500-kcs-bmc-v2"))
kcs_bmc = aspeed_kcs_probe_of_v2(pdev);
else
ops = of_device_get_match_data(&pdev->dev);
if (!ops)
return -EINVAL;
if (IS_ERR(kcs_bmc))
return PTR_ERR(kcs_bmc);
channel = ops->get_channel(pdev);
if (channel < 0)
return channel;
kcs_bmc->io_inputb = aspeed_kcs_inb;
kcs_bmc->io_outputb = aspeed_kcs_outb;
nr_addrs = ops->get_io_address(pdev, addrs);
if (nr_addrs < 0)
return nr_addrs;
rc = aspeed_kcs_config_irq(kcs_bmc, pdev);
np = pdev->dev.of_node;
rc = of_property_read_u32_array(np, "aspeed,lpc-interrupts", upstream_irq, 2);
if (rc && rc != -EINVAL)
return -EINVAL;
have_upstream_irq = !rc;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
kcs_bmc = &priv->kcs_bmc;
kcs_bmc->dev = &pdev->dev;
kcs_bmc->channel = channel;
kcs_bmc->ioreg = ast_kcs_bmc_ioregs[channel - 1];
kcs_bmc->ops = &aspeed_kcs_ops;
priv->map = syscon_node_to_regmap(pdev->dev.parent->of_node);
if (IS_ERR(priv->map)) {
dev_err(&pdev->dev, "Couldn't get regmap\n");
return -ENODEV;
}
spin_lock_init(&priv->obe.lock);
priv->obe.remove = false;
timer_setup(&priv->obe.timer, aspeed_kcs_check_obe, 0);
rc = aspeed_kcs_set_address(kcs_bmc, addrs, nr_addrs);
if (rc)
return rc;
dev_set_drvdata(dev, kcs_bmc);
/* Host to BMC IRQ */
rc = aspeed_kcs_config_downstream_irq(kcs_bmc, pdev);
if (rc)
return rc;
/* BMC to Host IRQ */
if (have_upstream_irq) {
rc = aspeed_kcs_config_upstream_irq(priv, upstream_irq[0], upstream_irq[1]);
if (rc < 0)
return rc;
} else {
priv->upstream_irq.mode = aspeed_kcs_irq_none;
}
platform_set_drvdata(pdev, priv);
aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0);
aspeed_kcs_enable_channel(kcs_bmc, true);
rc = misc_register(&kcs_bmc->miscdev);
rc = kcs_bmc_add_device(&priv->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;
}
dev_dbg(&pdev->dev,
"Probed KCS device %d (IDR=0x%x, ODR=0x%x, STR=0x%x)\n",
kcs_bmc->channel, kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr,
kcs_bmc->ioreg.str);
dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n",
kcs_bmc->channel, addrs[0]);
return 0;
}
static int aspeed_kcs_remove(struct platform_device *pdev)
{
struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
struct aspeed_kcs_bmc *priv = platform_get_drvdata(pdev);
struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc;
misc_deregister(&kcs_bmc->miscdev);
kcs_bmc_remove_device(kcs_bmc);
aspeed_kcs_enable_channel(kcs_bmc, false);
aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0);
/* Make sure it's proper dead */
spin_lock_irq(&priv->obe.lock);
priv->obe.remove = true;
spin_unlock_irq(&priv->obe.lock);
del_timer_sync(&priv->obe.timer);
return 0;
}
static const struct aspeed_kcs_of_ops of_v1_ops = {
.get_channel = aspeed_kcs_of_v1_get_channel,
.get_io_address = aspeed_kcs_of_v1_get_io_address,
};
static const struct aspeed_kcs_of_ops of_v2_ops = {
.get_channel = aspeed_kcs_of_v2_get_channel,
.get_io_address = aspeed_kcs_of_v2_get_io_address,
};
static const struct of_device_id ast_kcs_bmc_match[] = {
{ .compatible = "aspeed,ast2400-kcs-bmc" },
{ .compatible = "aspeed,ast2500-kcs-bmc" },
{ .compatible = "aspeed,ast2400-kcs-bmc-v2" },
{ .compatible = "aspeed,ast2500-kcs-bmc-v2" },
{ .compatible = "aspeed,ast2400-kcs-bmc", .data = &of_v1_ops },
{ .compatible = "aspeed,ast2500-kcs-bmc", .data = &of_v1_ops },
{ .compatible = "aspeed,ast2400-kcs-bmc-v2", .data = &of_v2_ops },
{ .compatible = "aspeed,ast2500-kcs-bmc-v2", .data = &of_v2_ops },
{ }
};
MODULE_DEVICE_TABLE(of, ast_kcs_bmc_match);
@ -421,4 +709,5 @@ module_platform_driver(ast_kcs_bmc_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
MODULE_DESCRIPTION("Aspeed device interface to the KCS BMC device");

View File

@ -0,0 +1,568 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015-2018, Intel Corporation.
*/
#define pr_fmt(fmt) "kcs-bmc: " fmt
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/ipmi_bmc.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "kcs_bmc_client.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_ipmi_phases {
KCS_PHASE_IDLE,
KCS_PHASE_WRITE_START,
KCS_PHASE_WRITE_DATA,
KCS_PHASE_WRITE_END_CMD,
KCS_PHASE_WRITE_DONE,
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_ipmi_errors {
KCS_NO_ERROR = 0x00,
KCS_ABORTED_BY_COMMAND = 0x01,
KCS_ILLEGAL_CONTROL_CODE = 0x02,
KCS_LENGTH_ERROR = 0x06,
KCS_UNSPECIFIED_ERROR = 0xFF
};
struct kcs_bmc_ipmi {
struct list_head entry;
struct kcs_bmc_client client;
spinlock_t lock;
enum kcs_ipmi_phases phase;
enum kcs_ipmi_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;
struct miscdevice miscdev;
};
#define DEVICE_NAME "ipmi-kcs"
#define KCS_MSG_BUFSIZ 1000
#define KCS_ZERO_DATA 0
/* IPMI 2.0 - Table 9-1, KCS Interface Status Register Bits */
#define KCS_STATUS_STATE(state) (state << 6)
#define KCS_STATUS_STATE_MASK GENMASK(7, 6)
#define KCS_STATUS_CMD_DAT BIT(3)
#define KCS_STATUS_SMS_ATN BIT(2)
#define KCS_STATUS_IBF BIT(1)
#define KCS_STATUS_OBF BIT(0)
/* IPMI 2.0 - Table 9-2, KCS Interface State Bits */
enum kcs_states {
IDLE_STATE = 0,
READ_STATE = 1,
WRITE_STATE = 2,
ERROR_STATE = 3,
};
/* IPMI 2.0 - Table 9-3, KCS Interface Control Codes */
#define KCS_CMD_GET_STATUS_ABORT 0x60
#define KCS_CMD_WRITE_START 0x61
#define KCS_CMD_WRITE_END 0x62
#define KCS_CMD_READ_BYTE 0x68
static inline void set_state(struct kcs_bmc_ipmi *priv, u8 state)
{
kcs_bmc_update_status(priv->client.dev, KCS_STATUS_STATE_MASK, KCS_STATUS_STATE(state));
}
static void kcs_bmc_ipmi_force_abort(struct kcs_bmc_ipmi *priv)
{
set_state(priv, ERROR_STATE);
kcs_bmc_read_data(priv->client.dev);
kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);
priv->phase = KCS_PHASE_ERROR;
priv->data_in_avail = false;
priv->data_in_idx = 0;
}
static void kcs_bmc_ipmi_handle_data(struct kcs_bmc_ipmi *priv)
{
struct kcs_bmc_device *dev;
u8 data;
dev = priv->client.dev;
switch (priv->phase) {
case KCS_PHASE_WRITE_START:
priv->phase = KCS_PHASE_WRITE_DATA;
fallthrough;
case KCS_PHASE_WRITE_DATA:
if (priv->data_in_idx < KCS_MSG_BUFSIZ) {
set_state(priv, WRITE_STATE);
kcs_bmc_write_data(dev, KCS_ZERO_DATA);
priv->data_in[priv->data_in_idx++] = kcs_bmc_read_data(dev);
} else {
kcs_bmc_ipmi_force_abort(priv);
priv->error = KCS_LENGTH_ERROR;
}
break;
case KCS_PHASE_WRITE_END_CMD:
if (priv->data_in_idx < KCS_MSG_BUFSIZ) {
set_state(priv, READ_STATE);
priv->data_in[priv->data_in_idx++] = kcs_bmc_read_data(dev);
priv->phase = KCS_PHASE_WRITE_DONE;
priv->data_in_avail = true;
wake_up_interruptible(&priv->queue);
} else {
kcs_bmc_ipmi_force_abort(priv);
priv->error = KCS_LENGTH_ERROR;
}
break;
case KCS_PHASE_READ:
if (priv->data_out_idx == priv->data_out_len)
set_state(priv, IDLE_STATE);
data = kcs_bmc_read_data(dev);
if (data != KCS_CMD_READ_BYTE) {
set_state(priv, ERROR_STATE);
kcs_bmc_write_data(dev, KCS_ZERO_DATA);
break;
}
if (priv->data_out_idx == priv->data_out_len) {
kcs_bmc_write_data(dev, KCS_ZERO_DATA);
priv->phase = KCS_PHASE_IDLE;
break;
}
kcs_bmc_write_data(dev, priv->data_out[priv->data_out_idx++]);
break;
case KCS_PHASE_ABORT_ERROR1:
set_state(priv, READ_STATE);
kcs_bmc_read_data(dev);
kcs_bmc_write_data(dev, priv->error);
priv->phase = KCS_PHASE_ABORT_ERROR2;
break;
case KCS_PHASE_ABORT_ERROR2:
set_state(priv, IDLE_STATE);
kcs_bmc_read_data(dev);
kcs_bmc_write_data(dev, KCS_ZERO_DATA);
priv->phase = KCS_PHASE_IDLE;
break;
default:
kcs_bmc_ipmi_force_abort(priv);
break;
}
}
static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc_ipmi *priv)
{
u8 cmd;
set_state(priv, WRITE_STATE);
kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);
cmd = kcs_bmc_read_data(priv->client.dev);
switch (cmd) {
case KCS_CMD_WRITE_START:
priv->phase = KCS_PHASE_WRITE_START;
priv->error = KCS_NO_ERROR;
priv->data_in_avail = false;
priv->data_in_idx = 0;
break;
case KCS_CMD_WRITE_END:
if (priv->phase != KCS_PHASE_WRITE_DATA) {
kcs_bmc_ipmi_force_abort(priv);
break;
}
priv->phase = KCS_PHASE_WRITE_END_CMD;
break;
case KCS_CMD_GET_STATUS_ABORT:
if (priv->error == KCS_NO_ERROR)
priv->error = KCS_ABORTED_BY_COMMAND;
priv->phase = KCS_PHASE_ABORT_ERROR1;
priv->data_in_avail = false;
priv->data_in_idx = 0;
break;
default:
kcs_bmc_ipmi_force_abort(priv);
priv->error = KCS_ILLEGAL_CONTROL_CODE;
break;
}
}
static inline struct kcs_bmc_ipmi *client_to_kcs_bmc_ipmi(struct kcs_bmc_client *client)
{
return container_of(client, struct kcs_bmc_ipmi, client);
}
static irqreturn_t kcs_bmc_ipmi_event(struct kcs_bmc_client *client)
{
struct kcs_bmc_ipmi *priv;
u8 status;
int ret;
priv = client_to_kcs_bmc_ipmi(client);
if (!priv)
return IRQ_NONE;
spin_lock(&priv->lock);
status = kcs_bmc_read_status(client->dev);
if (status & KCS_STATUS_IBF) {
if (status & KCS_STATUS_CMD_DAT)
kcs_bmc_ipmi_handle_cmd(priv);
else
kcs_bmc_ipmi_handle_data(priv);
ret = IRQ_HANDLED;
} else {
ret = IRQ_NONE;
}
spin_unlock(&priv->lock);
return ret;
}
static const struct kcs_bmc_client_ops kcs_bmc_ipmi_client_ops = {
.event = kcs_bmc_ipmi_event,
};
static inline struct kcs_bmc_ipmi *to_kcs_bmc(struct file *filp)
{
return container_of(filp->private_data, struct kcs_bmc_ipmi, miscdev);
}
static int kcs_bmc_ipmi_open(struct inode *inode, struct file *filp)
{
struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
return kcs_bmc_enable_device(priv->client.dev, &priv->client);
}
static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
{
struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
__poll_t mask = 0;
poll_wait(filp, &priv->queue, wait);
spin_lock_irq(&priv->lock);
if (priv->data_in_avail)
mask |= EPOLLIN;
spin_unlock_irq(&priv->lock);
return mask;
}
static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
bool data_avail;
size_t data_len;
ssize_t ret;
if (!(filp->f_flags & O_NONBLOCK))
wait_event_interruptible(priv->queue,
priv->data_in_avail);
mutex_lock(&priv->mutex);
spin_lock_irq(&priv->lock);
data_avail = priv->data_in_avail;
if (data_avail) {
data_len = priv->data_in_idx;
memcpy(priv->kbuffer, priv->data_in, data_len);
}
spin_unlock_irq(&priv->lock);
if (!data_avail) {
ret = -EAGAIN;
goto out_unlock;
}
if (count < data_len) {
pr_err("channel=%u with too large data : %zu\n",
priv->client.dev->channel, data_len);
spin_lock_irq(&priv->lock);
kcs_bmc_ipmi_force_abort(priv);
spin_unlock_irq(&priv->lock);
ret = -EOVERFLOW;
goto out_unlock;
}
if (copy_to_user(buf, priv->kbuffer, data_len)) {
ret = -EFAULT;
goto out_unlock;
}
ret = data_len;
spin_lock_irq(&priv->lock);
if (priv->phase == KCS_PHASE_WRITE_DONE) {
priv->phase = KCS_PHASE_WAIT_READ;
priv->data_in_avail = false;
priv->data_in_idx = 0;
} else {
ret = -EAGAIN;
}
spin_unlock_irq(&priv->lock);
out_unlock:
mutex_unlock(&priv->mutex);
return ret;
}
static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
ssize_t ret;
/* a minimum response size '3' : netfn + cmd + ccode */
if (count < 3 || count > KCS_MSG_BUFSIZ)
return -EINVAL;
mutex_lock(&priv->mutex);
if (copy_from_user(priv->kbuffer, buf, count)) {
ret = -EFAULT;
goto out_unlock;
}
spin_lock_irq(&priv->lock);
if (priv->phase == KCS_PHASE_WAIT_READ) {
priv->phase = KCS_PHASE_READ;
priv->data_out_idx = 1;
priv->data_out_len = count;
memcpy(priv->data_out, priv->kbuffer, count);
kcs_bmc_write_data(priv->client.dev, priv->data_out[0]);
ret = count;
} else {
ret = -EINVAL;
}
spin_unlock_irq(&priv->lock);
out_unlock:
mutex_unlock(&priv->mutex);
return ret;
}
static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
long ret = 0;
spin_lock_irq(&priv->lock);
switch (cmd) {
case IPMI_BMC_IOCTL_SET_SMS_ATN:
kcs_bmc_update_status(priv->client.dev, KCS_STATUS_SMS_ATN, KCS_STATUS_SMS_ATN);
break;
case IPMI_BMC_IOCTL_CLEAR_SMS_ATN:
kcs_bmc_update_status(priv->client.dev, KCS_STATUS_SMS_ATN, 0);
break;
case IPMI_BMC_IOCTL_FORCE_ABORT:
kcs_bmc_ipmi_force_abort(priv);
break;
default:
ret = -EINVAL;
break;
}
spin_unlock_irq(&priv->lock);
return ret;
}
static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
{
struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
kcs_bmc_ipmi_force_abort(priv);
kcs_bmc_disable_device(priv->client.dev, &priv->client);
return 0;
}
static const struct file_operations kcs_bmc_ipmi_fops = {
.owner = THIS_MODULE,
.open = kcs_bmc_ipmi_open,
.read = kcs_bmc_ipmi_read,
.write = kcs_bmc_ipmi_write,
.release = kcs_bmc_ipmi_release,
.poll = kcs_bmc_ipmi_poll,
.unlocked_ioctl = kcs_bmc_ipmi_ioctl,
};
static DEFINE_SPINLOCK(kcs_bmc_ipmi_instances_lock);
static LIST_HEAD(kcs_bmc_ipmi_instances);
static int kcs_bmc_ipmi_add_device(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_ipmi *priv;
int rc;
priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
spin_lock_init(&priv->lock);
mutex_init(&priv->mutex);
init_waitqueue_head(&priv->queue);
priv->client.dev = kcs_bmc;
priv->client.ops = &kcs_bmc_ipmi_client_ops;
priv->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
priv->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
priv->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
priv->miscdev.minor = MISC_DYNAMIC_MINOR;
priv->miscdev.name = devm_kasprintf(kcs_bmc->dev, GFP_KERNEL, "%s%u", DEVICE_NAME,
kcs_bmc->channel);
if (!priv->data_in || !priv->data_out || !priv->kbuffer || !priv->miscdev.name)
return -EINVAL;
priv->miscdev.fops = &kcs_bmc_ipmi_fops;
rc = misc_register(&priv->miscdev);
if (rc) {
dev_err(kcs_bmc->dev, "Unable to register device: %d\n", rc);
return rc;
}
spin_lock_irq(&kcs_bmc_ipmi_instances_lock);
list_add(&priv->entry, &kcs_bmc_ipmi_instances);
spin_unlock_irq(&kcs_bmc_ipmi_instances_lock);
dev_info(kcs_bmc->dev, "Initialised IPMI client for channel %d", kcs_bmc->channel);
return 0;
}
static int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_ipmi *priv = NULL, *pos;
spin_lock_irq(&kcs_bmc_ipmi_instances_lock);
list_for_each_entry(pos, &kcs_bmc_ipmi_instances, entry) {
if (pos->client.dev == kcs_bmc) {
priv = pos;
list_del(&pos->entry);
break;
}
}
spin_unlock_irq(&kcs_bmc_ipmi_instances_lock);
if (!priv)
return -ENODEV;
misc_deregister(&priv->miscdev);
kcs_bmc_disable_device(priv->client.dev, &priv->client);
devm_kfree(kcs_bmc->dev, priv->kbuffer);
devm_kfree(kcs_bmc->dev, priv->data_out);
devm_kfree(kcs_bmc->dev, priv->data_in);
devm_kfree(kcs_bmc->dev, priv);
return 0;
}
static const struct kcs_bmc_driver_ops kcs_bmc_ipmi_driver_ops = {
.add_device = kcs_bmc_ipmi_add_device,
.remove_device = kcs_bmc_ipmi_remove_device,
};
static struct kcs_bmc_driver kcs_bmc_ipmi_driver = {
.ops = &kcs_bmc_ipmi_driver_ops,
};
static int kcs_bmc_ipmi_init(void)
{
kcs_bmc_register_driver(&kcs_bmc_ipmi_driver);
return 0;
}
module_init(kcs_bmc_ipmi_init);
static void kcs_bmc_ipmi_exit(void)
{
kcs_bmc_unregister_driver(&kcs_bmc_ipmi_driver);
}
module_exit(kcs_bmc_ipmi_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");

View File

@ -0,0 +1,45 @@
/* 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

View File

@ -0,0 +1,22 @@
/* 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

View File

@ -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)
{
struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(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 = 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 (!kcs_bmc_handle_event(kcs_bmc))
return IRQ_HANDLED;
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);
return IRQ_NONE;
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 int npcm7xx_kcs_config_irq(struct kcs_bmc *kcs_bmc,
static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
{
struct kcs_bmc_device *kcs_bmc = arg;
return kcs_bmc_handle_event(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;
misc_deregister(&kcs_bmc->miscdev);
kcs_bmc_remove_device(kcs_bmc);
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;
}

View File

@ -0,0 +1,157 @@
// 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");