xref: /linux/arch/powerpc/platforms/83xx/mcu_mpc8349emitx.c (revision cdd5b5a9761fd66d17586e4f4ba6588c70e640ea)
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