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