linux/arch/powerpc/platforms/83xx/mcu_mpc8349emitx.c
<<
>>
Prefs
   1/*
   2 * Power Management and GPIO expander driver for MPC8349E-mITX-compatible MCU
   3 *
   4 * Copyright (c) 2008  MontaVista Software, Inc.
   5 *
   6 * Author: Anton Vorontsov <avorontsov@ru.mvista.com>
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License as published by
  10 * the Free Software Foundation; either version 2 of the License, or
  11 * (at your option) any later version.
  12 */
  13
  14#include <linux/kernel.h>
  15#include <linux/module.h>
  16#include <linux/device.h>
  17#include <linux/mutex.h>
  18#include <linux/i2c.h>
  19#include <linux/gpio/driver.h>
  20#include <linux/of.h>
  21#include <linux/of_gpio.h>
  22#include <linux/slab.h>
  23#include <linux/kthread.h>
  24#include <linux/reboot.h>
  25#include <asm/prom.h>
  26#include <asm/machdep.h>
  27
  28/*
  29 * I don't have specifications for the MCU firmware, I found this register
  30 * and bits positions by the trial&error method.
  31 */
  32#define MCU_REG_CTRL    0x20
  33#define MCU_CTRL_POFF   0x40
  34#define MCU_CTRL_BTN    0x80
  35
  36#define MCU_NUM_GPIO    2
  37
  38struct mcu {
  39        struct mutex lock;
  40        struct i2c_client *client;
  41        struct gpio_chip gc;
  42        u8 reg_ctrl;
  43};
  44
  45static struct mcu *glob_mcu;
  46
  47struct task_struct *shutdown_thread;
  48static int shutdown_thread_fn(void *data)
  49{
  50        int ret;
  51        struct mcu *mcu = glob_mcu;
  52
  53        while (!kthread_should_stop()) {
  54                ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL);
  55                if (ret < 0)
  56                        pr_err("MCU status reg read failed.\n");
  57                mcu->reg_ctrl = ret;
  58
  59
  60                if (mcu->reg_ctrl & MCU_CTRL_BTN) {
  61                        i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL,
  62                                                  mcu->reg_ctrl & ~MCU_CTRL_BTN);
  63
  64                        ctrl_alt_del();
  65                }
  66
  67                set_current_state(TASK_INTERRUPTIBLE);
  68                schedule_timeout(HZ);
  69        }
  70
  71        return 0;
  72}
  73
  74static ssize_t show_status(struct device *d,
  75                           struct device_attribute *attr, char *buf)
  76{
  77        int ret;
  78        struct mcu *mcu = glob_mcu;
  79
  80        ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL);
  81        if (ret < 0)
  82                return -ENODEV;
  83        mcu->reg_ctrl = ret;
  84
  85        return sprintf(buf, "%02x\n", ret);
  86}
  87static DEVICE_ATTR(status, S_IRUGO, show_status, NULL);
  88
  89static void mcu_power_off(void)
  90{
  91        struct mcu *mcu = glob_mcu;
  92
  93        pr_info("Sending power-off request to the MCU...\n");
  94        mutex_lock(&mcu->lock);
  95        i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL,
  96                                  mcu->reg_ctrl | MCU_CTRL_POFF);
  97        mutex_unlock(&mcu->lock);
  98}
  99
 100static void mcu_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
 101{
 102        struct mcu *mcu = gpiochip_get_data(gc);
 103        u8 bit = 1 << (4 + gpio);
 104
 105        mutex_lock(&mcu->lock);
 106        if (val)
 107                mcu->reg_ctrl &= ~bit;
 108        else
 109                mcu->reg_ctrl |= bit;
 110
 111        i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL, mcu->reg_ctrl);
 112        mutex_unlock(&mcu->lock);
 113}
 114
 115static int mcu_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
 116{
 117        mcu_gpio_set(gc, gpio, val);
 118        return 0;
 119}
 120
 121static int mcu_gpiochip_add(struct mcu *mcu)
 122{
 123        struct device_node *np;
 124        struct gpio_chip *gc = &mcu->gc;
 125
 126        np = of_find_compatible_node(NULL, NULL, "fsl,mcu-mpc8349emitx");
 127        if (!np)
 128                return -ENODEV;
 129
 130        gc->owner = THIS_MODULE;
 131        gc->label = np->full_name;
 132        gc->can_sleep = 1;
 133        gc->ngpio = MCU_NUM_GPIO;
 134        gc->base = -1;
 135        gc->set = mcu_gpio_set;
 136        gc->direction_output = mcu_gpio_dir_out;
 137        gc->of_node = np;
 138
 139        return gpiochip_add_data(gc, mcu);
 140}
 141
 142static int mcu_gpiochip_remove(struct mcu *mcu)
 143{
 144        gpiochip_remove(&mcu->gc);
 145        return 0;
 146}
 147
 148static int mcu_probe(struct i2c_client *client, const struct i2c_device_id *id)
 149{
 150        struct mcu *mcu;
 151        int ret;
 152
 153        mcu = kzalloc(sizeof(*mcu), GFP_KERNEL);
 154        if (!mcu)
 155                return -ENOMEM;
 156
 157        mutex_init(&mcu->lock);
 158        mcu->client = client;
 159        i2c_set_clientdata(client, mcu);
 160
 161        ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL);
 162        if (ret < 0)
 163                goto err;
 164        mcu->reg_ctrl = ret;
 165
 166        ret = mcu_gpiochip_add(mcu);
 167        if (ret)
 168                goto err;
 169
 170        /* XXX: this is potentially racy, but there is no lock for pm_power_off */
 171        if (!pm_power_off) {
 172                glob_mcu = mcu;
 173                pm_power_off = mcu_power_off;
 174                dev_info(&client->dev, "will provide power-off service\n");
 175        }
 176
 177        if (device_create_file(&client->dev, &dev_attr_status))
 178                dev_err(&client->dev,
 179                        "couldn't create device file for status\n");
 180
 181        shutdown_thread = kthread_run(shutdown_thread_fn, NULL,
 182                                      "mcu-i2c-shdn");
 183
 184        return 0;
 185err:
 186        kfree(mcu);
 187        return ret;
 188}
 189
 190static int mcu_remove(struct i2c_client *client)
 191{
 192        struct mcu *mcu = i2c_get_clientdata(client);
 193        int ret;
 194
 195        kthread_stop(shutdown_thread);
 196
 197        device_remove_file(&client->dev, &dev_attr_status);
 198
 199        if (glob_mcu == mcu) {
 200                pm_power_off = NULL;
 201                glob_mcu = NULL;
 202        }
 203
 204        ret = mcu_gpiochip_remove(mcu);
 205        if (ret)
 206                return ret;
 207        kfree(mcu);
 208        return 0;
 209}
 210
 211static const struct i2c_device_id mcu_ids[] = {
 212        { "mcu-mpc8349emitx", },
 213        {},
 214};
 215MODULE_DEVICE_TABLE(i2c, mcu_ids);
 216
 217static const struct of_device_id mcu_of_match_table[] = {
 218        { .compatible = "fsl,mcu-mpc8349emitx", },
 219        { },
 220};
 221
 222static struct i2c_driver mcu_driver = {
 223        .driver = {
 224                .name = "mcu-mpc8349emitx",
 225                .owner = THIS_MODULE,
 226                .of_match_table = mcu_of_match_table,
 227        },
 228        .probe = mcu_probe,
 229        .remove = mcu_remove,
 230        .id_table = mcu_ids,
 231};
 232
 233module_i2c_driver(mcu_driver);
 234
 235MODULE_DESCRIPTION("Power Management and GPIO expander driver for "
 236                   "MPC8349E-mITX-compatible MCU");
 237MODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>");
 238MODULE_LICENSE("GPL");
 239