linux/drivers/i2c/busses/i2c-scmi.c
<<
>>
Prefs
   1/*
   2 * SMBus driver for ACPI SMBus CMI
   3 *
   4 * Copyright (C) 2009 Crane Cai <crane.cai@amd.com>
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License as published by
   8 * the Free Software Foundation version 2.
   9 */
  10
  11#include <linux/module.h>
  12#include <linux/slab.h>
  13#include <linux/kernel.h>
  14#include <linux/stddef.h>
  15#include <linux/init.h>
  16#include <linux/i2c.h>
  17#include <linux/acpi.h>
  18
  19#define ACPI_SMBUS_HC_CLASS             "smbus"
  20#define ACPI_SMBUS_HC_DEVICE_NAME       "cmi"
  21
  22ACPI_MODULE_NAME("smbus_cmi");
  23
  24struct smbus_methods_t {
  25        char *mt_info;
  26        char *mt_sbr;
  27        char *mt_sbw;
  28};
  29
  30struct acpi_smbus_cmi {
  31        acpi_handle handle;
  32        struct i2c_adapter adapter;
  33        u8 cap_info:1;
  34        u8 cap_read:1;
  35        u8 cap_write:1;
  36};
  37
  38static const struct smbus_methods_t smbus_methods = {
  39        .mt_info = "_SBI",
  40        .mt_sbr  = "_SBR",
  41        .mt_sbw  = "_SBW",
  42};
  43
  44static const struct acpi_device_id acpi_smbus_cmi_ids[] = {
  45        {"SMBUS01", 0},
  46        {"", 0}
  47};
  48
  49#define ACPI_SMBUS_STATUS_OK                    0x00
  50#define ACPI_SMBUS_STATUS_FAIL                  0x07
  51#define ACPI_SMBUS_STATUS_DNAK                  0x10
  52#define ACPI_SMBUS_STATUS_DERR                  0x11
  53#define ACPI_SMBUS_STATUS_CMD_DENY              0x12
  54#define ACPI_SMBUS_STATUS_UNKNOWN               0x13
  55#define ACPI_SMBUS_STATUS_ACC_DENY              0x17
  56#define ACPI_SMBUS_STATUS_TIMEOUT               0x18
  57#define ACPI_SMBUS_STATUS_NOTSUP                0x19
  58#define ACPI_SMBUS_STATUS_BUSY                  0x1a
  59#define ACPI_SMBUS_STATUS_PEC                   0x1f
  60
  61#define ACPI_SMBUS_PRTCL_WRITE                  0x00
  62#define ACPI_SMBUS_PRTCL_READ                   0x01
  63#define ACPI_SMBUS_PRTCL_QUICK                  0x02
  64#define ACPI_SMBUS_PRTCL_BYTE                   0x04
  65#define ACPI_SMBUS_PRTCL_BYTE_DATA              0x06
  66#define ACPI_SMBUS_PRTCL_WORD_DATA              0x08
  67#define ACPI_SMBUS_PRTCL_BLOCK_DATA             0x0a
  68
  69
  70static int
  71acpi_smbus_cmi_access(struct i2c_adapter *adap, u16 addr, unsigned short flags,
  72                   char read_write, u8 command, int size,
  73                   union i2c_smbus_data *data)
  74{
  75        int result = 0;
  76        struct acpi_smbus_cmi *smbus_cmi = adap->algo_data;
  77        unsigned char protocol;
  78        acpi_status status = 0;
  79        struct acpi_object_list input;
  80        union acpi_object mt_params[5];
  81        struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
  82        union acpi_object *obj;
  83        union acpi_object *pkg;
  84        char *method;
  85        int len = 0;
  86
  87        dev_dbg(&adap->dev, "access size: %d %s\n", size,
  88                (read_write) ? "READ" : "WRITE");
  89        switch (size) {
  90        case I2C_SMBUS_QUICK:
  91                protocol = ACPI_SMBUS_PRTCL_QUICK;
  92                command = 0;
  93                if (read_write == I2C_SMBUS_WRITE) {
  94                        mt_params[3].type = ACPI_TYPE_INTEGER;
  95                        mt_params[3].integer.value = 0;
  96                        mt_params[4].type = ACPI_TYPE_INTEGER;
  97                        mt_params[4].integer.value = 0;
  98                }
  99                break;
 100
 101        case I2C_SMBUS_BYTE:
 102                protocol = ACPI_SMBUS_PRTCL_BYTE;
 103                if (read_write == I2C_SMBUS_WRITE) {
 104                        mt_params[3].type = ACPI_TYPE_INTEGER;
 105                        mt_params[3].integer.value = 0;
 106                        mt_params[4].type = ACPI_TYPE_INTEGER;
 107                        mt_params[4].integer.value = 0;
 108                } else {
 109                        command = 0;
 110                }
 111                break;
 112
 113        case I2C_SMBUS_BYTE_DATA:
 114                protocol = ACPI_SMBUS_PRTCL_BYTE_DATA;
 115                if (read_write == I2C_SMBUS_WRITE) {
 116                        mt_params[3].type = ACPI_TYPE_INTEGER;
 117                        mt_params[3].integer.value = 1;
 118                        mt_params[4].type = ACPI_TYPE_INTEGER;
 119                        mt_params[4].integer.value = data->byte;
 120                }
 121                break;
 122
 123        case I2C_SMBUS_WORD_DATA:
 124                protocol = ACPI_SMBUS_PRTCL_WORD_DATA;
 125                if (read_write == I2C_SMBUS_WRITE) {
 126                        mt_params[3].type = ACPI_TYPE_INTEGER;
 127                        mt_params[3].integer.value = 2;
 128                        mt_params[4].type = ACPI_TYPE_INTEGER;
 129                        mt_params[4].integer.value = data->word;
 130                }
 131                break;
 132
 133        case I2C_SMBUS_BLOCK_DATA:
 134                protocol = ACPI_SMBUS_PRTCL_BLOCK_DATA;
 135                if (read_write == I2C_SMBUS_WRITE) {
 136                        len = data->block[0];
 137                        if (len == 0 || len > I2C_SMBUS_BLOCK_MAX)
 138                                return -EINVAL;
 139                        mt_params[3].type = ACPI_TYPE_INTEGER;
 140                        mt_params[3].integer.value = len;
 141                        mt_params[4].type = ACPI_TYPE_BUFFER;
 142                        mt_params[4].buffer.pointer = data->block + 1;
 143                }
 144                break;
 145
 146        default:
 147                dev_warn(&adap->dev, "Unsupported transaction %d\n", size);
 148                return -EOPNOTSUPP;
 149        }
 150
 151        if (read_write == I2C_SMBUS_READ) {
 152                protocol |= ACPI_SMBUS_PRTCL_READ;
 153                method = smbus_methods.mt_sbr;
 154                input.count = 3;
 155        } else {
 156                protocol |= ACPI_SMBUS_PRTCL_WRITE;
 157                method = smbus_methods.mt_sbw;
 158                input.count = 5;
 159        }
 160
 161        input.pointer = mt_params;
 162        mt_params[0].type = ACPI_TYPE_INTEGER;
 163        mt_params[0].integer.value = protocol;
 164        mt_params[1].type = ACPI_TYPE_INTEGER;
 165        mt_params[1].integer.value = addr;
 166        mt_params[2].type = ACPI_TYPE_INTEGER;
 167        mt_params[2].integer.value = command;
 168
 169        status = acpi_evaluate_object(smbus_cmi->handle, method, &input,
 170                                      &buffer);
 171        if (ACPI_FAILURE(status)) {
 172                ACPI_ERROR((AE_INFO, "Evaluating %s: %i", method, status));
 173                return -EIO;
 174        }
 175
 176        pkg = buffer.pointer;
 177        if (pkg && pkg->type == ACPI_TYPE_PACKAGE)
 178                obj = pkg->package.elements;
 179        else {
 180                ACPI_ERROR((AE_INFO, "Invalid argument type"));
 181                result = -EIO;
 182                goto out;
 183        }
 184        if (obj == NULL || obj->type != ACPI_TYPE_INTEGER) {
 185                ACPI_ERROR((AE_INFO, "Invalid argument type"));
 186                result = -EIO;
 187                goto out;
 188        }
 189
 190        result = obj->integer.value;
 191        ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s return status: %i\n",
 192                          method, result));
 193
 194        switch (result) {
 195        case ACPI_SMBUS_STATUS_OK:
 196                result = 0;
 197                break;
 198        case ACPI_SMBUS_STATUS_BUSY:
 199                result = -EBUSY;
 200                goto out;
 201        case ACPI_SMBUS_STATUS_TIMEOUT:
 202                result = -ETIMEDOUT;
 203                goto out;
 204        case ACPI_SMBUS_STATUS_DNAK:
 205                result = -ENXIO;
 206                goto out;
 207        default:
 208                result = -EIO;
 209                goto out;
 210        }
 211
 212        if (read_write == I2C_SMBUS_WRITE || size == I2C_SMBUS_QUICK)
 213                goto out;
 214
 215        obj = pkg->package.elements + 1;
 216        if (obj == NULL || obj->type != ACPI_TYPE_INTEGER) {
 217                ACPI_ERROR((AE_INFO, "Invalid argument type"));
 218                result = -EIO;
 219                goto out;
 220        }
 221
 222        len = obj->integer.value;
 223        obj = pkg->package.elements + 2;
 224        switch (size) {
 225        case I2C_SMBUS_BYTE:
 226        case I2C_SMBUS_BYTE_DATA:
 227        case I2C_SMBUS_WORD_DATA:
 228                if (obj == NULL || obj->type != ACPI_TYPE_INTEGER) {
 229                        ACPI_ERROR((AE_INFO, "Invalid argument type"));
 230                        result = -EIO;
 231                        goto out;
 232                }
 233                if (len == 2)
 234                        data->word = obj->integer.value;
 235                else
 236                        data->byte = obj->integer.value;
 237                break;
 238        case I2C_SMBUS_BLOCK_DATA:
 239                if (obj == NULL || obj->type != ACPI_TYPE_BUFFER) {
 240                        ACPI_ERROR((AE_INFO, "Invalid argument type"));
 241                        result = -EIO;
 242                        goto out;
 243                }
 244                if (len == 0 || len > I2C_SMBUS_BLOCK_MAX)
 245                        return -EPROTO;
 246                data->block[0] = len;
 247                memcpy(data->block + 1, obj->buffer.pointer, len);
 248                break;
 249        }
 250
 251out:
 252        kfree(buffer.pointer);
 253        dev_dbg(&adap->dev, "Transaction status: %i\n", result);
 254        return result;
 255}
 256
 257static u32 acpi_smbus_cmi_func(struct i2c_adapter *adapter)
 258{
 259        struct acpi_smbus_cmi *smbus_cmi = adapter->algo_data;
 260        u32 ret;
 261
 262        ret = smbus_cmi->cap_read | smbus_cmi->cap_write ?
 263                I2C_FUNC_SMBUS_QUICK : 0;
 264
 265        ret |= smbus_cmi->cap_read ?
 266                (I2C_FUNC_SMBUS_READ_BYTE |
 267                I2C_FUNC_SMBUS_READ_BYTE_DATA |
 268                I2C_FUNC_SMBUS_READ_WORD_DATA |
 269                I2C_FUNC_SMBUS_READ_BLOCK_DATA) : 0;
 270
 271        ret |= smbus_cmi->cap_write ?
 272                (I2C_FUNC_SMBUS_WRITE_BYTE |
 273                I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
 274                I2C_FUNC_SMBUS_WRITE_WORD_DATA |
 275                I2C_FUNC_SMBUS_WRITE_BLOCK_DATA) : 0;
 276
 277        return ret;
 278}
 279
 280static const struct i2c_algorithm acpi_smbus_cmi_algorithm = {
 281        .smbus_xfer = acpi_smbus_cmi_access,
 282        .functionality = acpi_smbus_cmi_func,
 283};
 284
 285
 286static int acpi_smbus_cmi_add_cap(struct acpi_smbus_cmi *smbus_cmi,
 287                                  const char *name)
 288{
 289        struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
 290        union acpi_object *obj;
 291        acpi_status status;
 292
 293        if (!strcmp(name, smbus_methods.mt_info)) {
 294                status = acpi_evaluate_object(smbus_cmi->handle,
 295                                        smbus_methods.mt_info,
 296                                        NULL, &buffer);
 297                if (ACPI_FAILURE(status)) {
 298                        ACPI_ERROR((AE_INFO, "Evaluating %s: %i",
 299                                   smbus_methods.mt_info, status));
 300                        return -EIO;
 301                }
 302
 303                obj = buffer.pointer;
 304                if (obj && obj->type == ACPI_TYPE_PACKAGE)
 305                        obj = obj->package.elements;
 306                else {
 307                        ACPI_ERROR((AE_INFO, "Invalid argument type"));
 308                        kfree(buffer.pointer);
 309                        return -EIO;
 310                }
 311
 312                if (obj->type != ACPI_TYPE_INTEGER) {
 313                        ACPI_ERROR((AE_INFO, "Invalid argument type"));
 314                        kfree(buffer.pointer);
 315                        return -EIO;
 316                } else
 317                        ACPI_DEBUG_PRINT((ACPI_DB_INFO, "SMBus CMI Version %x"
 318                                          "\n", (int)obj->integer.value));
 319
 320                kfree(buffer.pointer);
 321                smbus_cmi->cap_info = 1;
 322        } else if (!strcmp(name, smbus_methods.mt_sbr))
 323                smbus_cmi->cap_read = 1;
 324        else if (!strcmp(name, smbus_methods.mt_sbw))
 325                smbus_cmi->cap_write = 1;
 326        else
 327                ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Unsupported CMI method: %s\n",
 328                                 name));
 329
 330        return 0;
 331}
 332
 333static acpi_status acpi_smbus_cmi_query_methods(acpi_handle handle, u32 level,
 334                        void *context, void **return_value)
 335{
 336        char node_name[5];
 337        struct acpi_buffer buffer = { sizeof(node_name), node_name };
 338        struct acpi_smbus_cmi *smbus_cmi = context;
 339        acpi_status status;
 340
 341        status = acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer);
 342
 343        if (ACPI_SUCCESS(status))
 344                acpi_smbus_cmi_add_cap(smbus_cmi, node_name);
 345
 346        return AE_OK;
 347}
 348
 349static int acpi_smbus_cmi_add(struct acpi_device *device)
 350{
 351        struct acpi_smbus_cmi *smbus_cmi;
 352
 353        smbus_cmi = kzalloc(sizeof(struct acpi_smbus_cmi), GFP_KERNEL);
 354        if (!smbus_cmi)
 355                return -ENOMEM;
 356
 357        smbus_cmi->handle = device->handle;
 358        strcpy(acpi_device_name(device), ACPI_SMBUS_HC_DEVICE_NAME);
 359        strcpy(acpi_device_class(device), ACPI_SMBUS_HC_CLASS);
 360        device->driver_data = smbus_cmi;
 361        smbus_cmi->cap_info = 0;
 362        smbus_cmi->cap_read = 0;
 363        smbus_cmi->cap_write = 0;
 364
 365        acpi_walk_namespace(ACPI_TYPE_METHOD, smbus_cmi->handle, 1,
 366                            acpi_smbus_cmi_query_methods, smbus_cmi, NULL);
 367
 368        if (smbus_cmi->cap_info == 0)
 369                goto err;
 370
 371        snprintf(smbus_cmi->adapter.name, sizeof(smbus_cmi->adapter.name),
 372                "SMBus CMI adapter %s",
 373                acpi_device_name(device));
 374        smbus_cmi->adapter.owner = THIS_MODULE;
 375        smbus_cmi->adapter.algo = &acpi_smbus_cmi_algorithm;
 376        smbus_cmi->adapter.algo_data = smbus_cmi;
 377        smbus_cmi->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
 378        smbus_cmi->adapter.dev.parent = &device->dev;
 379
 380        if (i2c_add_adapter(&smbus_cmi->adapter)) {
 381                dev_err(&device->dev, "Couldn't register adapter!\n");
 382                goto err;
 383        }
 384
 385        return 0;
 386
 387err:
 388        kfree(smbus_cmi);
 389        device->driver_data = NULL;
 390        return -EIO;
 391}
 392
 393static int acpi_smbus_cmi_remove(struct acpi_device *device, int type)
 394{
 395        struct acpi_smbus_cmi *smbus_cmi = acpi_driver_data(device);
 396
 397        i2c_del_adapter(&smbus_cmi->adapter);
 398        kfree(smbus_cmi);
 399        device->driver_data = NULL;
 400
 401        return 0;
 402}
 403
 404static struct acpi_driver acpi_smbus_cmi_driver = {
 405        .name = ACPI_SMBUS_HC_DEVICE_NAME,
 406        .class = ACPI_SMBUS_HC_CLASS,
 407        .ids = acpi_smbus_cmi_ids,
 408        .ops = {
 409                .add = acpi_smbus_cmi_add,
 410                .remove = acpi_smbus_cmi_remove,
 411        },
 412};
 413
 414static int __init acpi_smbus_cmi_init(void)
 415{
 416        return acpi_bus_register_driver(&acpi_smbus_cmi_driver);
 417}
 418
 419static void __exit acpi_smbus_cmi_exit(void)
 420{
 421        acpi_bus_unregister_driver(&acpi_smbus_cmi_driver);
 422}
 423
 424module_init(acpi_smbus_cmi_init);
 425module_exit(acpi_smbus_cmi_exit);
 426
 427MODULE_LICENSE("GPL");
 428MODULE_AUTHOR("Crane Cai <crane.cai@amd.com>");
 429MODULE_DESCRIPTION("ACPI SMBus CMI driver");
 430
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.