Path: blob/master/arch/powerpc/platforms/83xx/mcu_mpc8349emitx.c
26481 views
// SPDX-License-Identifier: GPL-2.0-or-later1/*2* Power Management and GPIO expander driver for MPC8349E-mITX-compatible MCU3*4* Copyright (c) 2008 MontaVista Software, Inc.5*6* Author: Anton Vorontsov <[email protected]>7*/89#include <linux/kernel.h>10#include <linux/mod_devicetable.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/slab.h>17#include <linux/kthread.h>18#include <linux/property.h>19#include <linux/reboot.h>20#include <asm/machdep.h>2122/*23* I don't have specifications for the MCU firmware, I found this register24* and bits positions by the trial&error method.25*/26#define MCU_REG_CTRL 0x2027#define MCU_CTRL_POFF 0x4028#define MCU_CTRL_BTN 0x802930#define MCU_NUM_GPIO 23132struct mcu {33struct mutex lock;34struct i2c_client *client;35struct gpio_chip gc;36u8 reg_ctrl;37};3839static struct mcu *glob_mcu;4041struct task_struct *shutdown_thread;42static int shutdown_thread_fn(void *data)43{44int ret;45struct mcu *mcu = glob_mcu;4647while (!kthread_should_stop()) {48ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL);49if (ret < 0)50pr_err("MCU status reg read failed.\n");51mcu->reg_ctrl = ret;525354if (mcu->reg_ctrl & MCU_CTRL_BTN) {55i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL,56mcu->reg_ctrl & ~MCU_CTRL_BTN);5758ctrl_alt_del();59}6061set_current_state(TASK_INTERRUPTIBLE);62schedule_timeout(HZ);63}6465return 0;66}6768static ssize_t show_status(struct device *d,69struct device_attribute *attr, char *buf)70{71int ret;72struct mcu *mcu = glob_mcu;7374ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL);75if (ret < 0)76return -ENODEV;77mcu->reg_ctrl = ret;7879return sprintf(buf, "%02x\n", ret);80}81static DEVICE_ATTR(status, 0444, show_status, NULL);8283static void mcu_power_off(void)84{85struct mcu *mcu = glob_mcu;8687pr_info("Sending power-off request to the MCU...\n");88mutex_lock(&mcu->lock);89i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL,90mcu->reg_ctrl | MCU_CTRL_POFF);91mutex_unlock(&mcu->lock);92}9394static int mcu_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)95{96struct mcu *mcu = gpiochip_get_data(gc);97u8 bit = 1 << (4 + gpio);98int ret;99100mutex_lock(&mcu->lock);101if (val)102mcu->reg_ctrl &= ~bit;103else104mcu->reg_ctrl |= bit;105106ret = i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL,107mcu->reg_ctrl);108mutex_unlock(&mcu->lock);109110return ret;111}112113static int mcu_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)114{115return mcu_gpio_set(gc, gpio, val);116}117118static int mcu_gpiochip_add(struct mcu *mcu)119{120struct device *dev = &mcu->client->dev;121struct gpio_chip *gc = &mcu->gc;122123gc->owner = THIS_MODULE;124gc->label = kasprintf(GFP_KERNEL, "%pfw", dev_fwnode(dev));125gc->can_sleep = 1;126gc->ngpio = MCU_NUM_GPIO;127gc->base = -1;128gc->set = mcu_gpio_set;129gc->direction_output = mcu_gpio_dir_out;130gc->parent = dev;131132return gpiochip_add_data(gc, mcu);133}134135static void mcu_gpiochip_remove(struct mcu *mcu)136{137kfree(mcu->gc.label);138gpiochip_remove(&mcu->gc);139}140141static int mcu_probe(struct i2c_client *client)142{143struct mcu *mcu;144int ret;145146mcu = kzalloc(sizeof(*mcu), GFP_KERNEL);147if (!mcu)148return -ENOMEM;149150mutex_init(&mcu->lock);151mcu->client = client;152i2c_set_clientdata(client, mcu);153154ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL);155if (ret < 0)156goto err;157mcu->reg_ctrl = ret;158159ret = mcu_gpiochip_add(mcu);160if (ret)161goto err;162163/* XXX: this is potentially racy, but there is no lock for pm_power_off */164if (!pm_power_off) {165glob_mcu = mcu;166pm_power_off = mcu_power_off;167dev_info(&client->dev, "will provide power-off service\n");168}169170if (device_create_file(&client->dev, &dev_attr_status))171dev_err(&client->dev,172"couldn't create device file for status\n");173174shutdown_thread = kthread_run(shutdown_thread_fn, NULL,175"mcu-i2c-shdn");176177return 0;178err:179kfree(mcu);180return ret;181}182183static void mcu_remove(struct i2c_client *client)184{185struct mcu *mcu = i2c_get_clientdata(client);186187kthread_stop(shutdown_thread);188189device_remove_file(&client->dev, &dev_attr_status);190191if (glob_mcu == mcu) {192pm_power_off = NULL;193glob_mcu = NULL;194}195196mcu_gpiochip_remove(mcu);197kfree(mcu);198}199200static const struct i2c_device_id mcu_ids[] = {201{ "mcu-mpc8349emitx", },202{},203};204MODULE_DEVICE_TABLE(i2c, mcu_ids);205206static const struct of_device_id mcu_of_match_table[] = {207{ .compatible = "fsl,mcu-mpc8349emitx", },208{ },209};210211static struct i2c_driver mcu_driver = {212.driver = {213.name = "mcu-mpc8349emitx",214.of_match_table = mcu_of_match_table,215},216.probe = mcu_probe,217.remove = mcu_remove,218.id_table = mcu_ids,219};220221module_i2c_driver(mcu_driver);222223MODULE_DESCRIPTION("Power Management and GPIO expander driver for "224"MPC8349E-mITX-compatible MCU");225MODULE_AUTHOR("Anton Vorontsov <[email protected]>");226MODULE_LICENSE("GPL");227228229