// SPDX-License-Identifier: GPL-2.0-only /* * drivers/hwmon/applesmc.c - driver for Apple's SMC (accelerometer, temperature * sensors, fan control, keyboard backlight control) used in Intel-based Apple * computers. * * Copyright (C) 2007 Nicolas Boichat <nicolas@boichat.ch> * Copyright (C) 2010 Henrik Rydberg <rydberg@euromail.se> * * Based on hdaps.c driver: * Copyright (C) 2005 Robert Love <rml@novell.com> * Copyright (C) 2005 Jesper Juhl <jj@chaosbits.net> * * Fan control based on smcFanControl: * Copyright (C) 2006 Hendrik Holtmann <holtmann@mac.com>
*/
/* data port used by Apple SMC */ #define APPLESMC_DATA_PORT 0x300 /* command/status port used by Apple SMC */ #define APPLESMC_CMD_PORT 0x304
#define APPLESMC_NR_PORTS 32 /* 0x300-0x31f */
#define APPLESMC_MAX_DATA_LENGTH 32
/* Apple SMC status bits */ #define SMC_STATUS_AWAITING_DATA BIT(0) /* SMC has data waiting to be read */ #define SMC_STATUS_IB_CLOSED BIT(1) /* Will ignore any input */ #define SMC_STATUS_BUSY BIT(2) /* Command in progress */
/* Initial wait is 8us */ #define APPLESMC_MIN_WAIT 0x0008
/* Dynamic device node group */ struct applesmc_node_group { char *format; /* format string */ void *show; /* show function */ void *store; /* store function */ int option; /* function argument */ struct applesmc_dev_attr *nodes; /* dynamic node array */
};
/* AppleSMC entry - cached register information */ struct applesmc_entry { char key[5]; /* four-letter key code */
u8 valid; /* set when entry is successfully read once */
u8 len; /* bounded by APPLESMC_MAX_DATA_LENGTH */ char type[5]; /* four-letter type code */
u8 flags; /* 0x10: func; 0x40: write; 0x80: read */
};
/* Register lookup and registers common to all SMCs */ staticstruct applesmc_registers { struct mutex mutex; /* register read/write mutex */ unsignedint key_count; /* number of SMC registers */ unsignedint fan_count; /* number of fans */ unsignedint temp_count; /* number of temperature registers */ unsignedint temp_begin; /* temperature lower index bound */ unsignedint temp_end; /* temperature upper index bound */ unsignedint index_count; /* size of temperature index array */ int num_light_sensors; /* number of light sensors */ bool has_accelerometer; /* has motion sensor */ bool has_key_backlight; /* has keyboard backlight */ bool init_complete; /* true when fully initialized */ struct applesmc_entry *cache; /* cached key entries */ constchar **index; /* temperature key index */
} smcreg = {
.mutex = __MUTEX_INITIALIZER(smcreg.mutex),
};
/* * Last index written to key_at_index sysfs file, and value to use for all other * key_at_index_* sysfs files.
*/ staticunsignedint key_at_index;
staticstruct workqueue_struct *applesmc_led_wq;
/* * Wait for specific status bits with a mask on the SMC. * Used before all transactions. * This does 10 fast loops of 8us then exponentially backs off for a * minimum total wait of 262ms. Depending on usleep_range this could * run out past 500ms.
*/
staticint wait_status(u8 val, u8 mask)
{
u8 status; int us; int i;
us = APPLESMC_MIN_WAIT; for (i = 0; i < 24 ; i++) {
status = inb(APPLESMC_CMD_PORT); if ((status & mask) == val) return 0;
usleep_range(us, us * 2); if (i > 9)
us <<= 1;
} return -EIO;
}
/* send_byte - Write to SMC data port. Callers must hold applesmc_lock. */
staticint send_byte(u8 cmd, u16 port)
{ int status;
status = wait_status(0, SMC_STATUS_IB_CLOSED); if (status) return status; /* * This needs to be a separate read looking for bit 0x04 * after bit 0x02 falls. If consolidated with the wait above * this extra read may not happen if status returns both * simultaneously and this would appear to be required.
*/
status = wait_status(SMC_STATUS_BUSY, SMC_STATUS_BUSY); if (status) return status;
outb(cmd, port); return 0;
}
/* send_command - Write a command to the SMC. Callers must hold applesmc_lock. */
staticint send_command(u8 cmd)
{ int ret;
ret = wait_status(0, SMC_STATUS_IB_CLOSED); if (ret) return ret;
outb(cmd, APPLESMC_CMD_PORT); return 0;
}
/* * Based on logic from the Apple driver. This is issued before any interaction * If busy is stuck high, issue a read command to reset the SMC state machine. * If busy is stuck high after the command then the SMC is jammed.
*/
staticint smc_sane(void)
{ int ret;
ret = wait_status(0, SMC_STATUS_BUSY); if (!ret) return ret;
ret = send_command(APPLESMC_READ_CMD); if (ret) return ret; return wait_status(0, SMC_STATUS_BUSY);
}
staticint send_argument(constchar *key)
{ int i;
for (i = 0; i < 4; i++) if (send_byte(key[i], APPLESMC_DATA_PORT)) return -EIO; return 0;
}
staticint read_smc(u8 cmd, constchar *key, u8 *buffer, u8 len)
{
u8 status, data = 0; int i; int ret;
/* This has no effect on newer (2012) SMCs */ if (send_byte(len, APPLESMC_DATA_PORT)) {
pr_warn("%.4s: read len fail\n", key); return -EIO;
}
for (i = 0; i < len; i++) { if (wait_status(SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY,
SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY)) {
pr_warn("%.4s: read data[%d] fail\n", key, i); return -EIO;
}
buffer[i] = inb(APPLESMC_DATA_PORT);
}
/* Read the data port until bit0 is cleared */ for (i = 0; i < 16; i++) {
udelay(APPLESMC_MIN_WAIT);
status = inb(APPLESMC_CMD_PORT); if (!(status & SMC_STATUS_AWAITING_DATA)) break;
data = inb(APPLESMC_DATA_PORT);
} if (i)
pr_warn("flushed %d bytes, last value is: %d\n", i, data);
return wait_status(0, SMC_STATUS_BUSY);
}
staticint write_smc(u8 cmd, constchar *key, const u8 *buffer, u8 len)
{ int i; int ret;
if (send_byte(len, APPLESMC_DATA_PORT)) {
pr_warn("%.4s: write len fail\n", key); return -EIO;
}
for (i = 0; i < len; i++) { if (send_byte(buffer[i], APPLESMC_DATA_PORT)) {
pr_warn("%s: write data fail\n", key); return -EIO;
}
}
return wait_status(0, SMC_STATUS_BUSY);
}
staticint read_register_count(unsignedint *count)
{
__be32 be; int ret;
ret = read_smc(APPLESMC_READ_CMD, KEY_COUNT_KEY, (u8 *)&be, 4); if (ret) return ret;
*count = be32_to_cpu(be); return 0;
}
/* * Serialized I/O * * Returns zero on success or a negative error on failure. * All functions below are concurrency safe - callers should NOT hold lock.
*/
if (entry->len != len) return -EINVAL;
mutex_lock(&smcreg.mutex);
ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buf, len);
mutex_unlock(&smcreg.mutex); return ret;
}
staticconststruct applesmc_entry *applesmc_get_entry_by_index(int index)
{ struct applesmc_entry *cache = &smcreg.cache[index];
u8 key[4], info[6];
__be32 be; int ret = 0;
if (cache->valid) return cache;
mutex_lock(&smcreg.mutex);
if (cache->valid) goto out;
be = cpu_to_be32(index);
ret = read_smc(APPLESMC_GET_KEY_BY_INDEX_CMD, (u8 *)&be, key, 4); if (ret) goto out;
ret = read_smc(APPLESMC_GET_KEY_TYPE_CMD, key, info, 6); if (ret) goto out;
out:
mutex_unlock(&smcreg.mutex); if (ret) return ERR_PTR(ret); return cache;
}
staticint applesmc_get_lower_bound(unsignedint *lo, constchar *key)
{ int begin = 0, end = smcreg.key_count; conststruct applesmc_entry *entry;
while (begin != end) { int middle = begin + (end - begin) / 2;
entry = applesmc_get_entry_by_index(middle); if (IS_ERR(entry)) {
*lo = 0; return PTR_ERR(entry);
} if (strcmp(entry->key, key) < 0)
begin = middle + 1; else
end = middle;
}
*lo = begin; return 0;
}
staticint applesmc_get_upper_bound(unsignedint *hi, constchar *key)
{ int begin = 0, end = smcreg.key_count; conststruct applesmc_entry *entry;
while (begin != end) { int middle = begin + (end - begin) / 2;
entry = applesmc_get_entry_by_index(middle); if (IS_ERR(entry)) {
*hi = smcreg.key_count; return PTR_ERR(entry);
} if (strcmp(key, entry->key) < 0)
end = middle; else
begin = middle + 1;
}
*hi = begin; return 0;
}
staticconststruct applesmc_entry *applesmc_get_entry_by_key(constchar *key)
{ int begin, end; int ret;
ret = applesmc_get_lower_bound(&begin, key); if (ret) return ERR_PTR(ret);
ret = applesmc_get_upper_bound(&end, key); if (ret) return ERR_PTR(ret); if (end - begin != 1) return ERR_PTR(-EINVAL);
s->index = kcalloc(s->temp_count, sizeof(s->index[0]), GFP_KERNEL); if (!s->index) return -ENOMEM;
for (i = s->temp_begin; i < s->temp_end; i++) {
entry = applesmc_get_entry_by_index(i); if (IS_ERR(entry)) continue; if (strcmp(entry->type, TEMP_SENSOR_TYPE)) continue;
s->index[s->index_count++] = entry->key;
}
ret = read_register_count(&count); if (ret) return ret;
if (s->cache && s->key_count != count) {
pr_warn("key count changed from %d to %d\n",
s->key_count, count);
kfree(s->cache);
s->cache = NULL;
}
s->key_count = count;
if (!s->cache)
s->cache = kcalloc(s->key_count, sizeof(*s->cache), GFP_KERNEL); if (!s->cache) return -ENOMEM;
ret = applesmc_read_key(FANS_COUNT, tmp, 1); if (ret) return ret;
s->fan_count = tmp[0]; if (s->fan_count > 10)
s->fan_count = 10;
ret = applesmc_get_lower_bound(&s->temp_begin, "T"); if (ret) return ret;
ret = applesmc_get_lower_bound(&s->temp_end, "U"); if (ret) return ret;
s->temp_count = s->temp_end - s->temp_begin;
ret = applesmc_init_index(s); if (ret) return ret;
ret = applesmc_has_key(LIGHT_SENSOR_LEFT_KEY, &left_light_sensor); if (ret) return ret;
ret = applesmc_has_key(LIGHT_SENSOR_RIGHT_KEY, &right_light_sensor); if (ret) return ret;
ret = applesmc_has_key(MOTION_SENSOR_KEY, &s->has_accelerometer); if (ret) return ret;
ret = applesmc_has_key(BACKLIGHT_KEY, &s->has_key_backlight); if (ret) return ret;
/* * applesmc_init_smcreg - Initialize register cache. * * Retries until initialization is successful, or the operation times out. *
*/ staticint applesmc_init_smcreg(void)
{ int ms, ret;
for (ms = 0; ms < INIT_TIMEOUT_MSECS; ms += INIT_WAIT_MSECS) {
ret = applesmc_init_smcreg_try(); if (!ret) { if (ms)
pr_info("init_smcreg() took %d ms\n", ms); return 0;
}
msleep(INIT_WAIT_MSECS);
}
applesmc_destroy_smcreg();
return ret;
}
/* Device model stuff */ staticint applesmc_probe(struct platform_device *dev)
{ int ret;
ret = applesmc_init_smcreg(); if (ret) return ret;
applesmc_device_init();
return 0;
}
/* Synchronize device with memorized backlight state */ staticint applesmc_pm_resume(struct device *dev)
{ if (smcreg.has_key_backlight)
applesmc_write_key(BACKLIGHT_KEY, backlight_state, 2); return 0;
}
/* Reinitialize device on resume from hibernation */ staticint applesmc_pm_restore(struct device *dev)
{
applesmc_device_init(); return applesmc_pm_resume(dev);
}
static ssize_t applesmc_position_show(struct device *dev, struct device_attribute *attr, char *buf)
{ int ret;
s16 x, y, z;
ret = applesmc_read_s16(MOTION_SENSOR_X_KEY, &x); if (ret) goto out;
ret = applesmc_read_s16(MOTION_SENSOR_Y_KEY, &y); if (ret) goto out;
ret = applesmc_read_s16(MOTION_SENSOR_Z_KEY, &z); if (ret) goto out;
out: if (ret) return ret;
return sysfs_emit(buf, "(%d,%d,%d)\n", x, y, z);
}
static ssize_t applesmc_light_show(struct device *dev, struct device_attribute *attr, char *sysfsbuf)
{ conststruct applesmc_entry *entry; staticint data_length; int ret;
u8 left = 0, right = 0;
u8 buffer[10];
if (!data_length) {
entry = applesmc_get_entry_by_key(LIGHT_SENSOR_LEFT_KEY); if (IS_ERR(entry)) return PTR_ERR(entry); if (entry->len > 10) return -ENXIO;
data_length = entry->len;
pr_info("light sensor data length set to %d\n", data_length);
}
ret = applesmc_read_key(LIGHT_SENSOR_LEFT_KEY, buffer, data_length); if (ret) goto out; /* newer macbooks report a single 10-bit bigendian value */ if (data_length == 10) {
left = be16_to_cpu(*(__be16 *)(buffer + 6)) >> 2; goto out;
}
left = buffer[2];
ret = applesmc_read_key(LIGHT_SENSOR_RIGHT_KEY, buffer, data_length); if (ret) goto out;
right = buffer[2];
/* Release all resources used by the accelerometer */ staticvoid applesmc_release_accelerometer(void)
{ if (!smcreg.has_accelerometer) return;
input_unregister_device(applesmc_idev);
applesmc_destroy_nodes(accelerometer_group);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.