162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  dcdbas.c: Dell Systems Management Base Driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  The Dell Systems Management Base Driver provides a sysfs interface for
662306a36Sopenharmony_ci *  systems management software to perform System Management Interrupts (SMIs)
762306a36Sopenharmony_ci *  and Host Control Actions (power cycle or power off after OS shutdown) on
862306a36Sopenharmony_ci *  Dell systems.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci *  See Documentation/driver-api/dcdbas.rst for more information.
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci *  Copyright (C) 1995-2006 Dell Inc.
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/acpi.h>
1762306a36Sopenharmony_ci#include <linux/dma-mapping.h>
1862306a36Sopenharmony_ci#include <linux/dmi.h>
1962306a36Sopenharmony_ci#include <linux/errno.h>
2062306a36Sopenharmony_ci#include <linux/cpu.h>
2162306a36Sopenharmony_ci#include <linux/gfp.h>
2262306a36Sopenharmony_ci#include <linux/init.h>
2362306a36Sopenharmony_ci#include <linux/io.h>
2462306a36Sopenharmony_ci#include <linux/kernel.h>
2562306a36Sopenharmony_ci#include <linux/mc146818rtc.h>
2662306a36Sopenharmony_ci#include <linux/module.h>
2762306a36Sopenharmony_ci#include <linux/reboot.h>
2862306a36Sopenharmony_ci#include <linux/sched.h>
2962306a36Sopenharmony_ci#include <linux/smp.h>
3062306a36Sopenharmony_ci#include <linux/spinlock.h>
3162306a36Sopenharmony_ci#include <linux/string.h>
3262306a36Sopenharmony_ci#include <linux/types.h>
3362306a36Sopenharmony_ci#include <linux/mutex.h>
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#include "dcdbas.h"
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define DRIVER_NAME		"dcdbas"
3862306a36Sopenharmony_ci#define DRIVER_VERSION		"5.6.0-3.4"
3962306a36Sopenharmony_ci#define DRIVER_DESCRIPTION	"Dell Systems Management Base Driver"
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic struct platform_device *dcdbas_pdev;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic unsigned long max_smi_data_buf_size = MAX_SMI_DATA_BUF_SIZE;
4462306a36Sopenharmony_cistatic DEFINE_MUTEX(smi_data_lock);
4562306a36Sopenharmony_cistatic u8 *bios_buffer;
4662306a36Sopenharmony_cistatic struct smi_buffer smi_buf;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic unsigned int host_control_action;
4962306a36Sopenharmony_cistatic unsigned int host_control_smi_type;
5062306a36Sopenharmony_cistatic unsigned int host_control_on_shutdown;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic bool wsmt_enabled;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ciint dcdbas_smi_alloc(struct smi_buffer *smi_buffer, unsigned long size)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	smi_buffer->virt = dma_alloc_coherent(&dcdbas_pdev->dev, size,
5762306a36Sopenharmony_ci					      &smi_buffer->dma, GFP_KERNEL);
5862306a36Sopenharmony_ci	if (!smi_buffer->virt) {
5962306a36Sopenharmony_ci		dev_dbg(&dcdbas_pdev->dev,
6062306a36Sopenharmony_ci			"%s: failed to allocate memory size %lu\n",
6162306a36Sopenharmony_ci			__func__, size);
6262306a36Sopenharmony_ci		return -ENOMEM;
6362306a36Sopenharmony_ci	}
6462306a36Sopenharmony_ci	smi_buffer->size = size;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n",
6762306a36Sopenharmony_ci		__func__, (u32)smi_buffer->dma, smi_buffer->size);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	return 0;
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dcdbas_smi_alloc);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_civoid dcdbas_smi_free(struct smi_buffer *smi_buffer)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	if (!smi_buffer->virt)
7662306a36Sopenharmony_ci		return;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n",
7962306a36Sopenharmony_ci		__func__, (u32)smi_buffer->dma, smi_buffer->size);
8062306a36Sopenharmony_ci	dma_free_coherent(&dcdbas_pdev->dev, smi_buffer->size,
8162306a36Sopenharmony_ci			  smi_buffer->virt, smi_buffer->dma);
8262306a36Sopenharmony_ci	smi_buffer->virt = NULL;
8362306a36Sopenharmony_ci	smi_buffer->dma = 0;
8462306a36Sopenharmony_ci	smi_buffer->size = 0;
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dcdbas_smi_free);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci/**
8962306a36Sopenharmony_ci * smi_data_buf_free: free SMI data buffer
9062306a36Sopenharmony_ci */
9162306a36Sopenharmony_cistatic void smi_data_buf_free(void)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	if (!smi_buf.virt || wsmt_enabled)
9462306a36Sopenharmony_ci		return;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	dcdbas_smi_free(&smi_buf);
9762306a36Sopenharmony_ci}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci/**
10062306a36Sopenharmony_ci * smi_data_buf_realloc: grow SMI data buffer if needed
10162306a36Sopenharmony_ci */
10262306a36Sopenharmony_cistatic int smi_data_buf_realloc(unsigned long size)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	struct smi_buffer tmp;
10562306a36Sopenharmony_ci	int ret;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	if (smi_buf.size >= size)
10862306a36Sopenharmony_ci		return 0;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	if (size > max_smi_data_buf_size)
11162306a36Sopenharmony_ci		return -EINVAL;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	/* new buffer is needed */
11462306a36Sopenharmony_ci	ret = dcdbas_smi_alloc(&tmp, size);
11562306a36Sopenharmony_ci	if (ret)
11662306a36Sopenharmony_ci		return ret;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	/* memory zeroed by dma_alloc_coherent */
11962306a36Sopenharmony_ci	if (smi_buf.virt)
12062306a36Sopenharmony_ci		memcpy(tmp.virt, smi_buf.virt, smi_buf.size);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	/* free any existing buffer */
12362306a36Sopenharmony_ci	smi_data_buf_free();
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	/* set up new buffer for use */
12662306a36Sopenharmony_ci	smi_buf = tmp;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	return 0;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic ssize_t smi_data_buf_phys_addr_show(struct device *dev,
13262306a36Sopenharmony_ci					   struct device_attribute *attr,
13362306a36Sopenharmony_ci					   char *buf)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	return sprintf(buf, "%x\n", (u32)smi_buf.dma);
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic ssize_t smi_data_buf_size_show(struct device *dev,
13962306a36Sopenharmony_ci				      struct device_attribute *attr,
14062306a36Sopenharmony_ci				      char *buf)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	return sprintf(buf, "%lu\n", smi_buf.size);
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic ssize_t smi_data_buf_size_store(struct device *dev,
14662306a36Sopenharmony_ci				       struct device_attribute *attr,
14762306a36Sopenharmony_ci				       const char *buf, size_t count)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	unsigned long buf_size;
15062306a36Sopenharmony_ci	ssize_t ret;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	buf_size = simple_strtoul(buf, NULL, 10);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	/* make sure SMI data buffer is at least buf_size */
15562306a36Sopenharmony_ci	mutex_lock(&smi_data_lock);
15662306a36Sopenharmony_ci	ret = smi_data_buf_realloc(buf_size);
15762306a36Sopenharmony_ci	mutex_unlock(&smi_data_lock);
15862306a36Sopenharmony_ci	if (ret)
15962306a36Sopenharmony_ci		return ret;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	return count;
16262306a36Sopenharmony_ci}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_cistatic ssize_t smi_data_read(struct file *filp, struct kobject *kobj,
16562306a36Sopenharmony_ci			     struct bin_attribute *bin_attr,
16662306a36Sopenharmony_ci			     char *buf, loff_t pos, size_t count)
16762306a36Sopenharmony_ci{
16862306a36Sopenharmony_ci	ssize_t ret;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	mutex_lock(&smi_data_lock);
17162306a36Sopenharmony_ci	ret = memory_read_from_buffer(buf, count, &pos, smi_buf.virt,
17262306a36Sopenharmony_ci					smi_buf.size);
17362306a36Sopenharmony_ci	mutex_unlock(&smi_data_lock);
17462306a36Sopenharmony_ci	return ret;
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic ssize_t smi_data_write(struct file *filp, struct kobject *kobj,
17862306a36Sopenharmony_ci			      struct bin_attribute *bin_attr,
17962306a36Sopenharmony_ci			      char *buf, loff_t pos, size_t count)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	ssize_t ret;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	if ((pos + count) > max_smi_data_buf_size)
18462306a36Sopenharmony_ci		return -EINVAL;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	mutex_lock(&smi_data_lock);
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	ret = smi_data_buf_realloc(pos + count);
18962306a36Sopenharmony_ci	if (ret)
19062306a36Sopenharmony_ci		goto out;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	memcpy(smi_buf.virt + pos, buf, count);
19362306a36Sopenharmony_ci	ret = count;
19462306a36Sopenharmony_ciout:
19562306a36Sopenharmony_ci	mutex_unlock(&smi_data_lock);
19662306a36Sopenharmony_ci	return ret;
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistatic ssize_t host_control_action_show(struct device *dev,
20062306a36Sopenharmony_ci					struct device_attribute *attr,
20162306a36Sopenharmony_ci					char *buf)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	return sprintf(buf, "%u\n", host_control_action);
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cistatic ssize_t host_control_action_store(struct device *dev,
20762306a36Sopenharmony_ci					 struct device_attribute *attr,
20862306a36Sopenharmony_ci					 const char *buf, size_t count)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci	ssize_t ret;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	/* make sure buffer is available for host control command */
21362306a36Sopenharmony_ci	mutex_lock(&smi_data_lock);
21462306a36Sopenharmony_ci	ret = smi_data_buf_realloc(sizeof(struct apm_cmd));
21562306a36Sopenharmony_ci	mutex_unlock(&smi_data_lock);
21662306a36Sopenharmony_ci	if (ret)
21762306a36Sopenharmony_ci		return ret;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	host_control_action = simple_strtoul(buf, NULL, 10);
22062306a36Sopenharmony_ci	return count;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_cistatic ssize_t host_control_smi_type_show(struct device *dev,
22462306a36Sopenharmony_ci					  struct device_attribute *attr,
22562306a36Sopenharmony_ci					  char *buf)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	return sprintf(buf, "%u\n", host_control_smi_type);
22862306a36Sopenharmony_ci}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic ssize_t host_control_smi_type_store(struct device *dev,
23162306a36Sopenharmony_ci					   struct device_attribute *attr,
23262306a36Sopenharmony_ci					   const char *buf, size_t count)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	host_control_smi_type = simple_strtoul(buf, NULL, 10);
23562306a36Sopenharmony_ci	return count;
23662306a36Sopenharmony_ci}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_cistatic ssize_t host_control_on_shutdown_show(struct device *dev,
23962306a36Sopenharmony_ci					     struct device_attribute *attr,
24062306a36Sopenharmony_ci					     char *buf)
24162306a36Sopenharmony_ci{
24262306a36Sopenharmony_ci	return sprintf(buf, "%u\n", host_control_on_shutdown);
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_cistatic ssize_t host_control_on_shutdown_store(struct device *dev,
24662306a36Sopenharmony_ci					      struct device_attribute *attr,
24762306a36Sopenharmony_ci					      const char *buf, size_t count)
24862306a36Sopenharmony_ci{
24962306a36Sopenharmony_ci	host_control_on_shutdown = simple_strtoul(buf, NULL, 10);
25062306a36Sopenharmony_ci	return count;
25162306a36Sopenharmony_ci}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_cistatic int raise_smi(void *par)
25462306a36Sopenharmony_ci{
25562306a36Sopenharmony_ci	struct smi_cmd *smi_cmd = par;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	if (smp_processor_id() != 0) {
25862306a36Sopenharmony_ci		dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n",
25962306a36Sopenharmony_ci			__func__);
26062306a36Sopenharmony_ci		return -EBUSY;
26162306a36Sopenharmony_ci	}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	/* generate SMI */
26462306a36Sopenharmony_ci	/* inb to force posted write through and make SMI happen now */
26562306a36Sopenharmony_ci	asm volatile (
26662306a36Sopenharmony_ci		"outb %b0,%w1\n"
26762306a36Sopenharmony_ci		"inb %w1"
26862306a36Sopenharmony_ci		: /* no output args */
26962306a36Sopenharmony_ci		: "a" (smi_cmd->command_code),
27062306a36Sopenharmony_ci		  "d" (smi_cmd->command_address),
27162306a36Sopenharmony_ci		  "b" (smi_cmd->ebx),
27262306a36Sopenharmony_ci		  "c" (smi_cmd->ecx)
27362306a36Sopenharmony_ci		: "memory"
27462306a36Sopenharmony_ci	);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	return 0;
27762306a36Sopenharmony_ci}
27862306a36Sopenharmony_ci/**
27962306a36Sopenharmony_ci * dcdbas_smi_request: generate SMI request
28062306a36Sopenharmony_ci *
28162306a36Sopenharmony_ci * Called with smi_data_lock.
28262306a36Sopenharmony_ci */
28362306a36Sopenharmony_ciint dcdbas_smi_request(struct smi_cmd *smi_cmd)
28462306a36Sopenharmony_ci{
28562306a36Sopenharmony_ci	int ret;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	if (smi_cmd->magic != SMI_CMD_MAGIC) {
28862306a36Sopenharmony_ci		dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n",
28962306a36Sopenharmony_ci			 __func__);
29062306a36Sopenharmony_ci		return -EBADR;
29162306a36Sopenharmony_ci	}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	/* SMI requires CPU 0 */
29462306a36Sopenharmony_ci	cpus_read_lock();
29562306a36Sopenharmony_ci	ret = smp_call_on_cpu(0, raise_smi, smi_cmd, true);
29662306a36Sopenharmony_ci	cpus_read_unlock();
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	return ret;
29962306a36Sopenharmony_ci}
30062306a36Sopenharmony_ciEXPORT_SYMBOL(dcdbas_smi_request);
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci/**
30362306a36Sopenharmony_ci * smi_request_store:
30462306a36Sopenharmony_ci *
30562306a36Sopenharmony_ci * The valid values are:
30662306a36Sopenharmony_ci * 0: zero SMI data buffer
30762306a36Sopenharmony_ci * 1: generate calling interface SMI
30862306a36Sopenharmony_ci * 2: generate raw SMI
30962306a36Sopenharmony_ci *
31062306a36Sopenharmony_ci * User application writes smi_cmd to smi_data before telling driver
31162306a36Sopenharmony_ci * to generate SMI.
31262306a36Sopenharmony_ci */
31362306a36Sopenharmony_cistatic ssize_t smi_request_store(struct device *dev,
31462306a36Sopenharmony_ci				 struct device_attribute *attr,
31562306a36Sopenharmony_ci				 const char *buf, size_t count)
31662306a36Sopenharmony_ci{
31762306a36Sopenharmony_ci	struct smi_cmd *smi_cmd;
31862306a36Sopenharmony_ci	unsigned long val = simple_strtoul(buf, NULL, 10);
31962306a36Sopenharmony_ci	ssize_t ret;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	mutex_lock(&smi_data_lock);
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	if (smi_buf.size < sizeof(struct smi_cmd)) {
32462306a36Sopenharmony_ci		ret = -ENODEV;
32562306a36Sopenharmony_ci		goto out;
32662306a36Sopenharmony_ci	}
32762306a36Sopenharmony_ci	smi_cmd = (struct smi_cmd *)smi_buf.virt;
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	switch (val) {
33062306a36Sopenharmony_ci	case 2:
33162306a36Sopenharmony_ci		/* Raw SMI */
33262306a36Sopenharmony_ci		ret = dcdbas_smi_request(smi_cmd);
33362306a36Sopenharmony_ci		if (!ret)
33462306a36Sopenharmony_ci			ret = count;
33562306a36Sopenharmony_ci		break;
33662306a36Sopenharmony_ci	case 1:
33762306a36Sopenharmony_ci		/*
33862306a36Sopenharmony_ci		 * Calling Interface SMI
33962306a36Sopenharmony_ci		 *
34062306a36Sopenharmony_ci		 * Provide physical address of command buffer field within
34162306a36Sopenharmony_ci		 * the struct smi_cmd to BIOS.
34262306a36Sopenharmony_ci		 *
34362306a36Sopenharmony_ci		 * Because the address that smi_cmd (smi_buf.virt) points to
34462306a36Sopenharmony_ci		 * will be from memremap() of a non-memory address if WSMT
34562306a36Sopenharmony_ci		 * is present, we can't use virt_to_phys() on smi_cmd, so
34662306a36Sopenharmony_ci		 * we have to use the physical address that was saved when
34762306a36Sopenharmony_ci		 * the virtual address for smi_cmd was received.
34862306a36Sopenharmony_ci		 */
34962306a36Sopenharmony_ci		smi_cmd->ebx = (u32)smi_buf.dma +
35062306a36Sopenharmony_ci				offsetof(struct smi_cmd, command_buffer);
35162306a36Sopenharmony_ci		ret = dcdbas_smi_request(smi_cmd);
35262306a36Sopenharmony_ci		if (!ret)
35362306a36Sopenharmony_ci			ret = count;
35462306a36Sopenharmony_ci		break;
35562306a36Sopenharmony_ci	case 0:
35662306a36Sopenharmony_ci		memset(smi_buf.virt, 0, smi_buf.size);
35762306a36Sopenharmony_ci		ret = count;
35862306a36Sopenharmony_ci		break;
35962306a36Sopenharmony_ci	default:
36062306a36Sopenharmony_ci		ret = -EINVAL;
36162306a36Sopenharmony_ci		break;
36262306a36Sopenharmony_ci	}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ciout:
36562306a36Sopenharmony_ci	mutex_unlock(&smi_data_lock);
36662306a36Sopenharmony_ci	return ret;
36762306a36Sopenharmony_ci}
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci/**
37062306a36Sopenharmony_ci * host_control_smi: generate host control SMI
37162306a36Sopenharmony_ci *
37262306a36Sopenharmony_ci * Caller must set up the host control command in smi_buf.virt.
37362306a36Sopenharmony_ci */
37462306a36Sopenharmony_cistatic int host_control_smi(void)
37562306a36Sopenharmony_ci{
37662306a36Sopenharmony_ci	struct apm_cmd *apm_cmd;
37762306a36Sopenharmony_ci	u8 *data;
37862306a36Sopenharmony_ci	unsigned long flags;
37962306a36Sopenharmony_ci	u32 num_ticks;
38062306a36Sopenharmony_ci	s8 cmd_status;
38162306a36Sopenharmony_ci	u8 index;
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	apm_cmd = (struct apm_cmd *)smi_buf.virt;
38462306a36Sopenharmony_ci	apm_cmd->status = ESM_STATUS_CMD_UNSUCCESSFUL;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	switch (host_control_smi_type) {
38762306a36Sopenharmony_ci	case HC_SMITYPE_TYPE1:
38862306a36Sopenharmony_ci		spin_lock_irqsave(&rtc_lock, flags);
38962306a36Sopenharmony_ci		/* write SMI data buffer physical address */
39062306a36Sopenharmony_ci		data = (u8 *)&smi_buf.dma;
39162306a36Sopenharmony_ci		for (index = PE1300_CMOS_CMD_STRUCT_PTR;
39262306a36Sopenharmony_ci		     index < (PE1300_CMOS_CMD_STRUCT_PTR + 4);
39362306a36Sopenharmony_ci		     index++, data++) {
39462306a36Sopenharmony_ci			outb(index,
39562306a36Sopenharmony_ci			     (CMOS_BASE_PORT + CMOS_PAGE2_INDEX_PORT_PIIX4));
39662306a36Sopenharmony_ci			outb(*data,
39762306a36Sopenharmony_ci			     (CMOS_BASE_PORT + CMOS_PAGE2_DATA_PORT_PIIX4));
39862306a36Sopenharmony_ci		}
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci		/* first set status to -1 as called by spec */
40162306a36Sopenharmony_ci		cmd_status = ESM_STATUS_CMD_UNSUCCESSFUL;
40262306a36Sopenharmony_ci		outb((u8) cmd_status, PCAT_APM_STATUS_PORT);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci		/* generate SMM call */
40562306a36Sopenharmony_ci		outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT);
40662306a36Sopenharmony_ci		spin_unlock_irqrestore(&rtc_lock, flags);
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci		/* wait a few to see if it executed */
40962306a36Sopenharmony_ci		num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING;
41062306a36Sopenharmony_ci		while ((s8)inb(PCAT_APM_STATUS_PORT) == ESM_STATUS_CMD_UNSUCCESSFUL) {
41162306a36Sopenharmony_ci			num_ticks--;
41262306a36Sopenharmony_ci			if (num_ticks == EXPIRED_TIMER)
41362306a36Sopenharmony_ci				return -ETIME;
41462306a36Sopenharmony_ci		}
41562306a36Sopenharmony_ci		break;
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	case HC_SMITYPE_TYPE2:
41862306a36Sopenharmony_ci	case HC_SMITYPE_TYPE3:
41962306a36Sopenharmony_ci		spin_lock_irqsave(&rtc_lock, flags);
42062306a36Sopenharmony_ci		/* write SMI data buffer physical address */
42162306a36Sopenharmony_ci		data = (u8 *)&smi_buf.dma;
42262306a36Sopenharmony_ci		for (index = PE1400_CMOS_CMD_STRUCT_PTR;
42362306a36Sopenharmony_ci		     index < (PE1400_CMOS_CMD_STRUCT_PTR + 4);
42462306a36Sopenharmony_ci		     index++, data++) {
42562306a36Sopenharmony_ci			outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT));
42662306a36Sopenharmony_ci			outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT));
42762306a36Sopenharmony_ci		}
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci		/* generate SMM call */
43062306a36Sopenharmony_ci		if (host_control_smi_type == HC_SMITYPE_TYPE3)
43162306a36Sopenharmony_ci			outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT);
43262306a36Sopenharmony_ci		else
43362306a36Sopenharmony_ci			outb(ESM_APM_CMD, PE1400_APM_CONTROL_PORT);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci		/* restore RTC index pointer since it was written to above */
43662306a36Sopenharmony_ci		CMOS_READ(RTC_REG_C);
43762306a36Sopenharmony_ci		spin_unlock_irqrestore(&rtc_lock, flags);
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci		/* read control port back to serialize write */
44062306a36Sopenharmony_ci		cmd_status = inb(PE1400_APM_CONTROL_PORT);
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci		/* wait a few to see if it executed */
44362306a36Sopenharmony_ci		num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING;
44462306a36Sopenharmony_ci		while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) {
44562306a36Sopenharmony_ci			num_ticks--;
44662306a36Sopenharmony_ci			if (num_ticks == EXPIRED_TIMER)
44762306a36Sopenharmony_ci				return -ETIME;
44862306a36Sopenharmony_ci		}
44962306a36Sopenharmony_ci		break;
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	default:
45262306a36Sopenharmony_ci		dev_dbg(&dcdbas_pdev->dev, "%s: invalid SMI type %u\n",
45362306a36Sopenharmony_ci			__func__, host_control_smi_type);
45462306a36Sopenharmony_ci		return -ENOSYS;
45562306a36Sopenharmony_ci	}
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	return 0;
45862306a36Sopenharmony_ci}
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci/**
46162306a36Sopenharmony_ci * dcdbas_host_control: initiate host control
46262306a36Sopenharmony_ci *
46362306a36Sopenharmony_ci * This function is called by the driver after the system has
46462306a36Sopenharmony_ci * finished shutting down if the user application specified a
46562306a36Sopenharmony_ci * host control action to perform on shutdown.  It is safe to
46662306a36Sopenharmony_ci * use smi_buf.virt at this point because the system has finished
46762306a36Sopenharmony_ci * shutting down and no userspace apps are running.
46862306a36Sopenharmony_ci */
46962306a36Sopenharmony_cistatic void dcdbas_host_control(void)
47062306a36Sopenharmony_ci{
47162306a36Sopenharmony_ci	struct apm_cmd *apm_cmd;
47262306a36Sopenharmony_ci	u8 action;
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	if (host_control_action == HC_ACTION_NONE)
47562306a36Sopenharmony_ci		return;
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	action = host_control_action;
47862306a36Sopenharmony_ci	host_control_action = HC_ACTION_NONE;
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	if (!smi_buf.virt) {
48162306a36Sopenharmony_ci		dev_dbg(&dcdbas_pdev->dev, "%s: no SMI buffer\n", __func__);
48262306a36Sopenharmony_ci		return;
48362306a36Sopenharmony_ci	}
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	if (smi_buf.size < sizeof(struct apm_cmd)) {
48662306a36Sopenharmony_ci		dev_dbg(&dcdbas_pdev->dev, "%s: SMI buffer too small\n",
48762306a36Sopenharmony_ci			__func__);
48862306a36Sopenharmony_ci		return;
48962306a36Sopenharmony_ci	}
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	apm_cmd = (struct apm_cmd *)smi_buf.virt;
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci	/* power off takes precedence */
49462306a36Sopenharmony_ci	if (action & HC_ACTION_HOST_CONTROL_POWEROFF) {
49562306a36Sopenharmony_ci		apm_cmd->command = ESM_APM_POWER_CYCLE;
49662306a36Sopenharmony_ci		apm_cmd->reserved = 0;
49762306a36Sopenharmony_ci		*((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 0;
49862306a36Sopenharmony_ci		host_control_smi();
49962306a36Sopenharmony_ci	} else if (action & HC_ACTION_HOST_CONTROL_POWERCYCLE) {
50062306a36Sopenharmony_ci		apm_cmd->command = ESM_APM_POWER_CYCLE;
50162306a36Sopenharmony_ci		apm_cmd->reserved = 0;
50262306a36Sopenharmony_ci		*((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 20;
50362306a36Sopenharmony_ci		host_control_smi();
50462306a36Sopenharmony_ci	}
50562306a36Sopenharmony_ci}
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_ci/* WSMT */
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_cistatic u8 checksum(u8 *buffer, u8 length)
51062306a36Sopenharmony_ci{
51162306a36Sopenharmony_ci	u8 sum = 0;
51262306a36Sopenharmony_ci	u8 *end = buffer + length;
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	while (buffer < end)
51562306a36Sopenharmony_ci		sum += *buffer++;
51662306a36Sopenharmony_ci	return sum;
51762306a36Sopenharmony_ci}
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_cistatic inline struct smm_eps_table *check_eps_table(u8 *addr)
52062306a36Sopenharmony_ci{
52162306a36Sopenharmony_ci	struct smm_eps_table *eps = (struct smm_eps_table *)addr;
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	if (strncmp(eps->smm_comm_buff_anchor, SMM_EPS_SIG, 4) != 0)
52462306a36Sopenharmony_ci		return NULL;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	if (checksum(addr, eps->length) != 0)
52762306a36Sopenharmony_ci		return NULL;
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	return eps;
53062306a36Sopenharmony_ci}
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_cistatic int dcdbas_check_wsmt(void)
53362306a36Sopenharmony_ci{
53462306a36Sopenharmony_ci	const struct dmi_device *dev = NULL;
53562306a36Sopenharmony_ci	struct acpi_table_wsmt *wsmt = NULL;
53662306a36Sopenharmony_ci	struct smm_eps_table *eps = NULL;
53762306a36Sopenharmony_ci	u64 bios_buf_paddr;
53862306a36Sopenharmony_ci	u64 remap_size;
53962306a36Sopenharmony_ci	u8 *addr;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	acpi_get_table(ACPI_SIG_WSMT, 0, (struct acpi_table_header **)&wsmt);
54262306a36Sopenharmony_ci	if (!wsmt)
54362306a36Sopenharmony_ci		return 0;
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	/* Check if WSMT ACPI table shows that protection is enabled */
54662306a36Sopenharmony_ci	if (!(wsmt->protection_flags & ACPI_WSMT_FIXED_COMM_BUFFERS) ||
54762306a36Sopenharmony_ci	    !(wsmt->protection_flags & ACPI_WSMT_COMM_BUFFER_NESTED_PTR_PROTECTION))
54862306a36Sopenharmony_ci		return 0;
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	/*
55162306a36Sopenharmony_ci	 * BIOS could provide the address/size of the protected buffer
55262306a36Sopenharmony_ci	 * in an SMBIOS string or in an EPS structure in 0xFxxxx.
55362306a36Sopenharmony_ci	 */
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci	/* Check SMBIOS for buffer address */
55662306a36Sopenharmony_ci	while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev)))
55762306a36Sopenharmony_ci		if (sscanf(dev->name, "30[%16llx;%8llx]", &bios_buf_paddr,
55862306a36Sopenharmony_ci		    &remap_size) == 2)
55962306a36Sopenharmony_ci			goto remap;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	/* Scan for EPS (entry point structure) */
56262306a36Sopenharmony_ci	for (addr = (u8 *)__va(0xf0000);
56362306a36Sopenharmony_ci	     addr < (u8 *)__va(0x100000 - sizeof(struct smm_eps_table));
56462306a36Sopenharmony_ci	     addr += 16) {
56562306a36Sopenharmony_ci		eps = check_eps_table(addr);
56662306a36Sopenharmony_ci		if (eps)
56762306a36Sopenharmony_ci			break;
56862306a36Sopenharmony_ci	}
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	if (!eps) {
57162306a36Sopenharmony_ci		dev_dbg(&dcdbas_pdev->dev, "found WSMT, but no firmware buffer found\n");
57262306a36Sopenharmony_ci		return -ENODEV;
57362306a36Sopenharmony_ci	}
57462306a36Sopenharmony_ci	bios_buf_paddr = eps->smm_comm_buff_addr;
57562306a36Sopenharmony_ci	remap_size = eps->num_of_4k_pages * PAGE_SIZE;
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ciremap:
57862306a36Sopenharmony_ci	/*
57962306a36Sopenharmony_ci	 * Get physical address of buffer and map to virtual address.
58062306a36Sopenharmony_ci	 * Table gives size in 4K pages, regardless of actual system page size.
58162306a36Sopenharmony_ci	 */
58262306a36Sopenharmony_ci	if (upper_32_bits(bios_buf_paddr + 8)) {
58362306a36Sopenharmony_ci		dev_warn(&dcdbas_pdev->dev, "found WSMT, but buffer address is above 4GB\n");
58462306a36Sopenharmony_ci		return -EINVAL;
58562306a36Sopenharmony_ci	}
58662306a36Sopenharmony_ci	/*
58762306a36Sopenharmony_ci	 * Limit remap size to MAX_SMI_DATA_BUF_SIZE + 8 (since the first 8
58862306a36Sopenharmony_ci	 * bytes are used for a semaphore, not the data buffer itself).
58962306a36Sopenharmony_ci	 */
59062306a36Sopenharmony_ci	if (remap_size > MAX_SMI_DATA_BUF_SIZE + 8)
59162306a36Sopenharmony_ci		remap_size = MAX_SMI_DATA_BUF_SIZE + 8;
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	bios_buffer = memremap(bios_buf_paddr, remap_size, MEMREMAP_WB);
59462306a36Sopenharmony_ci	if (!bios_buffer) {
59562306a36Sopenharmony_ci		dev_warn(&dcdbas_pdev->dev, "found WSMT, but failed to map buffer\n");
59662306a36Sopenharmony_ci		return -ENOMEM;
59762306a36Sopenharmony_ci	}
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci	/* First 8 bytes is for a semaphore, not part of the smi_buf.virt */
60062306a36Sopenharmony_ci	smi_buf.dma = bios_buf_paddr + 8;
60162306a36Sopenharmony_ci	smi_buf.virt = bios_buffer + 8;
60262306a36Sopenharmony_ci	smi_buf.size = remap_size - 8;
60362306a36Sopenharmony_ci	max_smi_data_buf_size = smi_buf.size;
60462306a36Sopenharmony_ci	wsmt_enabled = true;
60562306a36Sopenharmony_ci	dev_info(&dcdbas_pdev->dev,
60662306a36Sopenharmony_ci		 "WSMT found, using firmware-provided SMI buffer.\n");
60762306a36Sopenharmony_ci	return 1;
60862306a36Sopenharmony_ci}
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci/**
61162306a36Sopenharmony_ci * dcdbas_reboot_notify: handle reboot notification for host control
61262306a36Sopenharmony_ci */
61362306a36Sopenharmony_cistatic int dcdbas_reboot_notify(struct notifier_block *nb, unsigned long code,
61462306a36Sopenharmony_ci				void *unused)
61562306a36Sopenharmony_ci{
61662306a36Sopenharmony_ci	switch (code) {
61762306a36Sopenharmony_ci	case SYS_DOWN:
61862306a36Sopenharmony_ci	case SYS_HALT:
61962306a36Sopenharmony_ci	case SYS_POWER_OFF:
62062306a36Sopenharmony_ci		if (host_control_on_shutdown) {
62162306a36Sopenharmony_ci			/* firmware is going to perform host control action */
62262306a36Sopenharmony_ci			printk(KERN_WARNING "Please wait for shutdown "
62362306a36Sopenharmony_ci			       "action to complete...\n");
62462306a36Sopenharmony_ci			dcdbas_host_control();
62562306a36Sopenharmony_ci		}
62662306a36Sopenharmony_ci		break;
62762306a36Sopenharmony_ci	}
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci	return NOTIFY_DONE;
63062306a36Sopenharmony_ci}
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_cistatic struct notifier_block dcdbas_reboot_nb = {
63362306a36Sopenharmony_ci	.notifier_call = dcdbas_reboot_notify,
63462306a36Sopenharmony_ci	.next = NULL,
63562306a36Sopenharmony_ci	.priority = INT_MIN
63662306a36Sopenharmony_ci};
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_cistatic DCDBAS_BIN_ATTR_RW(smi_data);
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_cistatic struct bin_attribute *dcdbas_bin_attrs[] = {
64162306a36Sopenharmony_ci	&bin_attr_smi_data,
64262306a36Sopenharmony_ci	NULL
64362306a36Sopenharmony_ci};
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_cistatic DCDBAS_DEV_ATTR_RW(smi_data_buf_size);
64662306a36Sopenharmony_cistatic DCDBAS_DEV_ATTR_RO(smi_data_buf_phys_addr);
64762306a36Sopenharmony_cistatic DCDBAS_DEV_ATTR_WO(smi_request);
64862306a36Sopenharmony_cistatic DCDBAS_DEV_ATTR_RW(host_control_action);
64962306a36Sopenharmony_cistatic DCDBAS_DEV_ATTR_RW(host_control_smi_type);
65062306a36Sopenharmony_cistatic DCDBAS_DEV_ATTR_RW(host_control_on_shutdown);
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_cistatic struct attribute *dcdbas_dev_attrs[] = {
65362306a36Sopenharmony_ci	&dev_attr_smi_data_buf_size.attr,
65462306a36Sopenharmony_ci	&dev_attr_smi_data_buf_phys_addr.attr,
65562306a36Sopenharmony_ci	&dev_attr_smi_request.attr,
65662306a36Sopenharmony_ci	&dev_attr_host_control_action.attr,
65762306a36Sopenharmony_ci	&dev_attr_host_control_smi_type.attr,
65862306a36Sopenharmony_ci	&dev_attr_host_control_on_shutdown.attr,
65962306a36Sopenharmony_ci	NULL
66062306a36Sopenharmony_ci};
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_cistatic const struct attribute_group dcdbas_attr_group = {
66362306a36Sopenharmony_ci	.attrs = dcdbas_dev_attrs,
66462306a36Sopenharmony_ci	.bin_attrs = dcdbas_bin_attrs,
66562306a36Sopenharmony_ci};
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_cistatic int dcdbas_probe(struct platform_device *dev)
66862306a36Sopenharmony_ci{
66962306a36Sopenharmony_ci	int error;
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_ci	host_control_action = HC_ACTION_NONE;
67262306a36Sopenharmony_ci	host_control_smi_type = HC_SMITYPE_NONE;
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_ci	dcdbas_pdev = dev;
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	/* Check if ACPI WSMT table specifies protected SMI buffer address */
67762306a36Sopenharmony_ci	error = dcdbas_check_wsmt();
67862306a36Sopenharmony_ci	if (error < 0)
67962306a36Sopenharmony_ci		return error;
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_ci	/*
68262306a36Sopenharmony_ci	 * BIOS SMI calls require buffer addresses be in 32-bit address space.
68362306a36Sopenharmony_ci	 * This is done by setting the DMA mask below.
68462306a36Sopenharmony_ci	 */
68562306a36Sopenharmony_ci	error = dma_set_coherent_mask(&dcdbas_pdev->dev, DMA_BIT_MASK(32));
68662306a36Sopenharmony_ci	if (error)
68762306a36Sopenharmony_ci		return error;
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_ci	error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group);
69062306a36Sopenharmony_ci	if (error)
69162306a36Sopenharmony_ci		return error;
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	register_reboot_notifier(&dcdbas_reboot_nb);
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ci	dev_info(&dev->dev, "%s (version %s)\n",
69662306a36Sopenharmony_ci		 DRIVER_DESCRIPTION, DRIVER_VERSION);
69762306a36Sopenharmony_ci
69862306a36Sopenharmony_ci	return 0;
69962306a36Sopenharmony_ci}
70062306a36Sopenharmony_ci
70162306a36Sopenharmony_cistatic void dcdbas_remove(struct platform_device *dev)
70262306a36Sopenharmony_ci{
70362306a36Sopenharmony_ci	unregister_reboot_notifier(&dcdbas_reboot_nb);
70462306a36Sopenharmony_ci	sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group);
70562306a36Sopenharmony_ci}
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_cistatic struct platform_driver dcdbas_driver = {
70862306a36Sopenharmony_ci	.driver		= {
70962306a36Sopenharmony_ci		.name	= DRIVER_NAME,
71062306a36Sopenharmony_ci	},
71162306a36Sopenharmony_ci	.probe		= dcdbas_probe,
71262306a36Sopenharmony_ci	.remove_new	= dcdbas_remove,
71362306a36Sopenharmony_ci};
71462306a36Sopenharmony_ci
71562306a36Sopenharmony_cistatic const struct platform_device_info dcdbas_dev_info __initconst = {
71662306a36Sopenharmony_ci	.name		= DRIVER_NAME,
71762306a36Sopenharmony_ci	.id		= PLATFORM_DEVID_NONE,
71862306a36Sopenharmony_ci	.dma_mask	= DMA_BIT_MASK(32),
71962306a36Sopenharmony_ci};
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_cistatic struct platform_device *dcdbas_pdev_reg;
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_ci/**
72462306a36Sopenharmony_ci * dcdbas_init: initialize driver
72562306a36Sopenharmony_ci */
72662306a36Sopenharmony_cistatic int __init dcdbas_init(void)
72762306a36Sopenharmony_ci{
72862306a36Sopenharmony_ci	int error;
72962306a36Sopenharmony_ci
73062306a36Sopenharmony_ci	error = platform_driver_register(&dcdbas_driver);
73162306a36Sopenharmony_ci	if (error)
73262306a36Sopenharmony_ci		return error;
73362306a36Sopenharmony_ci
73462306a36Sopenharmony_ci	dcdbas_pdev_reg = platform_device_register_full(&dcdbas_dev_info);
73562306a36Sopenharmony_ci	if (IS_ERR(dcdbas_pdev_reg)) {
73662306a36Sopenharmony_ci		error = PTR_ERR(dcdbas_pdev_reg);
73762306a36Sopenharmony_ci		goto err_unregister_driver;
73862306a36Sopenharmony_ci	}
73962306a36Sopenharmony_ci
74062306a36Sopenharmony_ci	return 0;
74162306a36Sopenharmony_ci
74262306a36Sopenharmony_ci err_unregister_driver:
74362306a36Sopenharmony_ci	platform_driver_unregister(&dcdbas_driver);
74462306a36Sopenharmony_ci	return error;
74562306a36Sopenharmony_ci}
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci/**
74862306a36Sopenharmony_ci * dcdbas_exit: perform driver cleanup
74962306a36Sopenharmony_ci */
75062306a36Sopenharmony_cistatic void __exit dcdbas_exit(void)
75162306a36Sopenharmony_ci{
75262306a36Sopenharmony_ci	/*
75362306a36Sopenharmony_ci	 * make sure functions that use dcdbas_pdev are called
75462306a36Sopenharmony_ci	 * before platform_device_unregister
75562306a36Sopenharmony_ci	 */
75662306a36Sopenharmony_ci	unregister_reboot_notifier(&dcdbas_reboot_nb);
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci	/*
75962306a36Sopenharmony_ci	 * We have to free the buffer here instead of dcdbas_remove
76062306a36Sopenharmony_ci	 * because only in module exit function we can be sure that
76162306a36Sopenharmony_ci	 * all sysfs attributes belonging to this module have been
76262306a36Sopenharmony_ci	 * released.
76362306a36Sopenharmony_ci	 */
76462306a36Sopenharmony_ci	if (dcdbas_pdev)
76562306a36Sopenharmony_ci		smi_data_buf_free();
76662306a36Sopenharmony_ci	if (bios_buffer)
76762306a36Sopenharmony_ci		memunmap(bios_buffer);
76862306a36Sopenharmony_ci	platform_device_unregister(dcdbas_pdev_reg);
76962306a36Sopenharmony_ci	platform_driver_unregister(&dcdbas_driver);
77062306a36Sopenharmony_ci}
77162306a36Sopenharmony_ci
77262306a36Sopenharmony_cisubsys_initcall_sync(dcdbas_init);
77362306a36Sopenharmony_cimodule_exit(dcdbas_exit);
77462306a36Sopenharmony_ci
77562306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")");
77662306a36Sopenharmony_ciMODULE_VERSION(DRIVER_VERSION);
77762306a36Sopenharmony_ciMODULE_AUTHOR("Dell Inc.");
77862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
77962306a36Sopenharmony_ci/* Any System or BIOS claiming to be by Dell */
78062306a36Sopenharmony_ciMODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*");
781