12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2ea0105eaSAnton Vorontsov /*
3ea0105eaSAnton Vorontsov * Power Management and GPIO expander driver for MPC8349E-mITX-compatible MCU
4ea0105eaSAnton Vorontsov *
5ea0105eaSAnton Vorontsov * Copyright (c) 2008 MontaVista Software, Inc.
6ea0105eaSAnton Vorontsov *
7ea0105eaSAnton Vorontsov * Author: Anton Vorontsov <avorontsov@ru.mvista.com>
8ea0105eaSAnton Vorontsov */
9ea0105eaSAnton Vorontsov
10ea0105eaSAnton Vorontsov #include <linux/kernel.h>
11610cc9f4SAndy Shevchenko #include <linux/mod_devicetable.h>
12ea0105eaSAnton Vorontsov #include <linux/module.h>
13ea0105eaSAnton Vorontsov #include <linux/device.h>
14ea0105eaSAnton Vorontsov #include <linux/mutex.h>
15ea0105eaSAnton Vorontsov #include <linux/i2c.h>
16da5f767eSLinus Walleij #include <linux/gpio/driver.h>
175a0e3ad6STejun Heo #include <linux/slab.h>
186ca6ca5dSFabio Baltieri #include <linux/kthread.h>
19610cc9f4SAndy Shevchenko #include <linux/property.h>
206ca6ca5dSFabio Baltieri #include <linux/reboot.h>
21ea0105eaSAnton Vorontsov #include <asm/machdep.h>
22ea0105eaSAnton Vorontsov
23ea0105eaSAnton Vorontsov /*
24ea0105eaSAnton Vorontsov * I don't have specifications for the MCU firmware, I found this register
25ea0105eaSAnton Vorontsov * and bits positions by the trial&error method.
26ea0105eaSAnton Vorontsov */
27ea0105eaSAnton Vorontsov #define MCU_REG_CTRL 0x20
28ea0105eaSAnton Vorontsov #define MCU_CTRL_POFF 0x40
296ca6ca5dSFabio Baltieri #define MCU_CTRL_BTN 0x80
30ea0105eaSAnton Vorontsov
31ea0105eaSAnton Vorontsov #define MCU_NUM_GPIO 2
32ea0105eaSAnton Vorontsov
33ea0105eaSAnton Vorontsov struct mcu {
34ea0105eaSAnton Vorontsov struct mutex lock;
35ea0105eaSAnton Vorontsov struct i2c_client *client;
36a19e3da5SAnton Vorontsov struct gpio_chip gc;
37ea0105eaSAnton Vorontsov u8 reg_ctrl;
38ea0105eaSAnton Vorontsov };
39ea0105eaSAnton Vorontsov
40ea0105eaSAnton Vorontsov static struct mcu *glob_mcu;
41ea0105eaSAnton Vorontsov
426ca6ca5dSFabio Baltieri struct task_struct *shutdown_thread;
shutdown_thread_fn(void * data)436ca6ca5dSFabio Baltieri static int shutdown_thread_fn(void *data)
446ca6ca5dSFabio Baltieri {
456ca6ca5dSFabio Baltieri int ret;
466ca6ca5dSFabio Baltieri struct mcu *mcu = glob_mcu;
476ca6ca5dSFabio Baltieri
486ca6ca5dSFabio Baltieri while (!kthread_should_stop()) {
496ca6ca5dSFabio Baltieri ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL);
506ca6ca5dSFabio Baltieri if (ret < 0)
516ca6ca5dSFabio Baltieri pr_err("MCU status reg read failed.\n");
526ca6ca5dSFabio Baltieri mcu->reg_ctrl = ret;
536ca6ca5dSFabio Baltieri
546ca6ca5dSFabio Baltieri
556ca6ca5dSFabio Baltieri if (mcu->reg_ctrl & MCU_CTRL_BTN) {
566ca6ca5dSFabio Baltieri i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL,
576ca6ca5dSFabio Baltieri mcu->reg_ctrl & ~MCU_CTRL_BTN);
586ca6ca5dSFabio Baltieri
596ca6ca5dSFabio Baltieri ctrl_alt_del();
606ca6ca5dSFabio Baltieri }
616ca6ca5dSFabio Baltieri
626ca6ca5dSFabio Baltieri set_current_state(TASK_INTERRUPTIBLE);
636ca6ca5dSFabio Baltieri schedule_timeout(HZ);
646ca6ca5dSFabio Baltieri }
656ca6ca5dSFabio Baltieri
666ca6ca5dSFabio Baltieri return 0;
676ca6ca5dSFabio Baltieri }
686ca6ca5dSFabio Baltieri
show_status(struct device * d,struct device_attribute * attr,char * buf)696ca6ca5dSFabio Baltieri static ssize_t show_status(struct device *d,
706ca6ca5dSFabio Baltieri struct device_attribute *attr, char *buf)
716ca6ca5dSFabio Baltieri {
726ca6ca5dSFabio Baltieri int ret;
736ca6ca5dSFabio Baltieri struct mcu *mcu = glob_mcu;
746ca6ca5dSFabio Baltieri
756ca6ca5dSFabio Baltieri ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL);
766ca6ca5dSFabio Baltieri if (ret < 0)
776ca6ca5dSFabio Baltieri return -ENODEV;
786ca6ca5dSFabio Baltieri mcu->reg_ctrl = ret;
796ca6ca5dSFabio Baltieri
806ca6ca5dSFabio Baltieri return sprintf(buf, "%02x\n", ret);
816ca6ca5dSFabio Baltieri }
8257ad583fSRussell Currey static DEVICE_ATTR(status, 0444, show_status, NULL);
836ca6ca5dSFabio Baltieri
mcu_power_off(void)84ea0105eaSAnton Vorontsov static void mcu_power_off(void)
85ea0105eaSAnton Vorontsov {
86ea0105eaSAnton Vorontsov struct mcu *mcu = glob_mcu;
87ea0105eaSAnton Vorontsov
88ea0105eaSAnton Vorontsov pr_info("Sending power-off request to the MCU...\n");
89ea0105eaSAnton Vorontsov mutex_lock(&mcu->lock);
906ca6ca5dSFabio Baltieri i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL,
91ea0105eaSAnton Vorontsov mcu->reg_ctrl | MCU_CTRL_POFF);
92ea0105eaSAnton Vorontsov mutex_unlock(&mcu->lock);
93ea0105eaSAnton Vorontsov }
94ea0105eaSAnton Vorontsov
mcu_gpio_set(struct gpio_chip * gc,unsigned int gpio,int val)95ea0105eaSAnton Vorontsov static void mcu_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
96ea0105eaSAnton Vorontsov {
97da5f767eSLinus Walleij struct mcu *mcu = gpiochip_get_data(gc);
98ea0105eaSAnton Vorontsov u8 bit = 1 << (4 + gpio);
99ea0105eaSAnton Vorontsov
100ea0105eaSAnton Vorontsov mutex_lock(&mcu->lock);
101ea0105eaSAnton Vorontsov if (val)
102ea0105eaSAnton Vorontsov mcu->reg_ctrl &= ~bit;
103ea0105eaSAnton Vorontsov else
104ea0105eaSAnton Vorontsov mcu->reg_ctrl |= bit;
105ea0105eaSAnton Vorontsov
106ea0105eaSAnton Vorontsov i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL, mcu->reg_ctrl);
107ea0105eaSAnton Vorontsov mutex_unlock(&mcu->lock);
108ea0105eaSAnton Vorontsov }
109ea0105eaSAnton Vorontsov
mcu_gpio_dir_out(struct gpio_chip * gc,unsigned int gpio,int val)110ea0105eaSAnton Vorontsov static int mcu_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
111ea0105eaSAnton Vorontsov {
112ea0105eaSAnton Vorontsov mcu_gpio_set(gc, gpio, val);
113ea0105eaSAnton Vorontsov return 0;
114ea0105eaSAnton Vorontsov }
115ea0105eaSAnton Vorontsov
mcu_gpiochip_add(struct mcu * mcu)116ea0105eaSAnton Vorontsov static int mcu_gpiochip_add(struct mcu *mcu)
117ea0105eaSAnton Vorontsov {
118610cc9f4SAndy Shevchenko struct device *dev = &mcu->client->dev;
119a19e3da5SAnton Vorontsov struct gpio_chip *gc = &mcu->gc;
120ea0105eaSAnton Vorontsov
121ea0105eaSAnton Vorontsov gc->owner = THIS_MODULE;
122610cc9f4SAndy Shevchenko gc->label = kasprintf(GFP_KERNEL, "%pfw", dev_fwnode(dev));
123ea0105eaSAnton Vorontsov gc->can_sleep = 1;
124ea0105eaSAnton Vorontsov gc->ngpio = MCU_NUM_GPIO;
125ea0105eaSAnton Vorontsov gc->base = -1;
126ea0105eaSAnton Vorontsov gc->set = mcu_gpio_set;
127ea0105eaSAnton Vorontsov gc->direction_output = mcu_gpio_dir_out;
128610cc9f4SAndy Shevchenko gc->parent = dev;
129ea0105eaSAnton Vorontsov
130da5f767eSLinus Walleij return gpiochip_add_data(gc, mcu);
131ea0105eaSAnton Vorontsov }
132ea0105eaSAnton Vorontsov
mcu_gpiochip_remove(struct mcu * mcu)1335d354dc3SUwe Kleine-König static void mcu_gpiochip_remove(struct mcu *mcu)
134ea0105eaSAnton Vorontsov {
135b7c670d6SRob Herring kfree(mcu->gc.label);
13688d5e520Sabdoulaye berthe gpiochip_remove(&mcu->gc);
137ea0105eaSAnton Vorontsov }
138ea0105eaSAnton Vorontsov
mcu_probe(struct i2c_client * client)1396c9100eaSStephen Kitt static int mcu_probe(struct i2c_client *client)
140ea0105eaSAnton Vorontsov {
141ea0105eaSAnton Vorontsov struct mcu *mcu;
142ea0105eaSAnton Vorontsov int ret;
143ea0105eaSAnton Vorontsov
144ea0105eaSAnton Vorontsov mcu = kzalloc(sizeof(*mcu), GFP_KERNEL);
145ea0105eaSAnton Vorontsov if (!mcu)
146ea0105eaSAnton Vorontsov return -ENOMEM;
147ea0105eaSAnton Vorontsov
148ea0105eaSAnton Vorontsov mutex_init(&mcu->lock);
149ea0105eaSAnton Vorontsov mcu->client = client;
150ea0105eaSAnton Vorontsov i2c_set_clientdata(client, mcu);
151ea0105eaSAnton Vorontsov
152ea0105eaSAnton Vorontsov ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL);
153ea0105eaSAnton Vorontsov if (ret < 0)
154ea0105eaSAnton Vorontsov goto err;
155ea0105eaSAnton Vorontsov mcu->reg_ctrl = ret;
156ea0105eaSAnton Vorontsov
157ea0105eaSAnton Vorontsov ret = mcu_gpiochip_add(mcu);
158ea0105eaSAnton Vorontsov if (ret)
159ea0105eaSAnton Vorontsov goto err;
160ea0105eaSAnton Vorontsov
1619178ba29SAlexander Graf /* XXX: this is potentially racy, but there is no lock for pm_power_off */
1629178ba29SAlexander Graf if (!pm_power_off) {
163ea0105eaSAnton Vorontsov glob_mcu = mcu;
1649178ba29SAlexander Graf pm_power_off = mcu_power_off;
165ea0105eaSAnton Vorontsov dev_info(&client->dev, "will provide power-off service\n");
166ea0105eaSAnton Vorontsov }
167ea0105eaSAnton Vorontsov
1686ca6ca5dSFabio Baltieri if (device_create_file(&client->dev, &dev_attr_status))
1696ca6ca5dSFabio Baltieri dev_err(&client->dev,
1706ca6ca5dSFabio Baltieri "couldn't create device file for status\n");
1716ca6ca5dSFabio Baltieri
1726ca6ca5dSFabio Baltieri shutdown_thread = kthread_run(shutdown_thread_fn, NULL,
1736ca6ca5dSFabio Baltieri "mcu-i2c-shdn");
1746ca6ca5dSFabio Baltieri
175ea0105eaSAnton Vorontsov return 0;
176ea0105eaSAnton Vorontsov err:
177ea0105eaSAnton Vorontsov kfree(mcu);
178ea0105eaSAnton Vorontsov return ret;
179ea0105eaSAnton Vorontsov }
180ea0105eaSAnton Vorontsov
mcu_remove(struct i2c_client * client)181ed5c2f5fSUwe Kleine-König static void mcu_remove(struct i2c_client *client)
182ea0105eaSAnton Vorontsov {
183ea0105eaSAnton Vorontsov struct mcu *mcu = i2c_get_clientdata(client);
184ea0105eaSAnton Vorontsov
1856ca6ca5dSFabio Baltieri kthread_stop(shutdown_thread);
1866ca6ca5dSFabio Baltieri
1876ca6ca5dSFabio Baltieri device_remove_file(&client->dev, &dev_attr_status);
1886ca6ca5dSFabio Baltieri
189ea0105eaSAnton Vorontsov if (glob_mcu == mcu) {
1909178ba29SAlexander Graf pm_power_off = NULL;
191ea0105eaSAnton Vorontsov glob_mcu = NULL;
192ea0105eaSAnton Vorontsov }
193ea0105eaSAnton Vorontsov
1945d354dc3SUwe Kleine-König mcu_gpiochip_remove(mcu);
195ea0105eaSAnton Vorontsov kfree(mcu);
196ea0105eaSAnton Vorontsov }
197ea0105eaSAnton Vorontsov
198ea0105eaSAnton Vorontsov static const struct i2c_device_id mcu_ids[] = {
199ea0105eaSAnton Vorontsov { "mcu-mpc8349emitx", },
200ea0105eaSAnton Vorontsov {},
201ea0105eaSAnton Vorontsov };
202ea0105eaSAnton Vorontsov MODULE_DEVICE_TABLE(i2c, mcu_ids);
203ea0105eaSAnton Vorontsov
204ce6d73c9SUwe Kleine-König static const struct of_device_id mcu_of_match_table[] = {
2052ffe8c5fSGrant Likely { .compatible = "fsl,mcu-mpc8349emitx", },
2062ffe8c5fSGrant Likely { },
2072ffe8c5fSGrant Likely };
2082ffe8c5fSGrant Likely
209ea0105eaSAnton Vorontsov static struct i2c_driver mcu_driver = {
210ea0105eaSAnton Vorontsov .driver = {
211ea0105eaSAnton Vorontsov .name = "mcu-mpc8349emitx",
2122ffe8c5fSGrant Likely .of_match_table = mcu_of_match_table,
213ea0105eaSAnton Vorontsov },
214*48f2444eSUwe Kleine-König .probe = mcu_probe,
215cad5cef6SGreg Kroah-Hartman .remove = mcu_remove,
216ea0105eaSAnton Vorontsov .id_table = mcu_ids,
217ea0105eaSAnton Vorontsov };
218ea0105eaSAnton Vorontsov
21998c7355fSWei Yongjun module_i2c_driver(mcu_driver);
220ea0105eaSAnton Vorontsov
221ea0105eaSAnton Vorontsov MODULE_DESCRIPTION("Power Management and GPIO expander driver for "
222ea0105eaSAnton Vorontsov "MPC8349E-mITX-compatible MCU");
223ea0105eaSAnton Vorontsov MODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>");
224ea0105eaSAnton Vorontsov MODULE_LICENSE("GPL");
225