diff options
Diffstat (limited to 'drivers/input/keyboard/samsung-keypad.c')
| -rw-r--r-- | drivers/input/keyboard/samsung-keypad.c | 313 | 
1 files changed, 219 insertions, 94 deletions
diff --git a/drivers/input/keyboard/samsung-keypad.c b/drivers/input/keyboard/samsung-keypad.c index f689f49e310..5e80fbf7b5e 100644 --- a/drivers/input/keyboard/samsung-keypad.c +++ b/drivers/input/keyboard/samsung-keypad.c @@ -14,15 +14,17 @@  #include <linux/clk.h>  #include <linux/delay.h>  #include <linux/err.h> -#include <linux/init.h>  #include <linux/input.h>  #include <linux/interrupt.h>  #include <linux/io.h>  #include <linux/module.h>  #include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h>  #include <linux/slab.h> +#include <linux/of.h>  #include <linux/sched.h> -#include <plat/keypad.h> +#include <linux/input/samsung-keypad.h>  #define SAMSUNG_KEYIFCON			0x00  #define SAMSUNG_KEYIFSTSCLR			0x04 @@ -63,11 +65,14 @@ enum samsung_keypad_type {  struct samsung_keypad {  	struct input_dev *input_dev; +	struct platform_device *pdev;  	struct clk *clk;  	void __iomem *base;  	wait_queue_head_t wait;  	bool stopped; +	bool wake_enabled;  	int irq; +	enum samsung_keypad_type type;  	unsigned int row_shift;  	unsigned int rows;  	unsigned int cols; @@ -75,24 +80,14 @@ struct samsung_keypad {  	unsigned short keycodes[];  }; -static int samsung_keypad_is_s5pv210(struct device *dev) -{ -	struct platform_device *pdev = to_platform_device(dev); -	enum samsung_keypad_type type = -		platform_get_device_id(pdev)->driver_data; - -	return type == KEYPAD_TYPE_S5PV210; -} -  static void samsung_keypad_scan(struct samsung_keypad *keypad,  				unsigned int *row_state)  { -	struct device *dev = keypad->input_dev->dev.parent;  	unsigned int col;  	unsigned int val;  	for (col = 0; col < keypad->cols; col++) { -		if (samsung_keypad_is_s5pv210(dev)) { +		if (keypad->type == KEYPAD_TYPE_S5PV210) {  			val = S5PV210_KEYIFCOLEN_MASK;  			val &= ~(1 << col) << 8;  		} else { @@ -158,6 +153,8 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id)  	unsigned int val;  	bool key_down; +	pm_runtime_get_sync(&keypad->pdev->dev); +  	do {  		val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR);  		/* Clear interrupt. */ @@ -172,6 +169,8 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id)  	} while (key_down && !keypad->stopped); +	pm_runtime_put(&keypad->pdev->dev); +  	return IRQ_HANDLED;  } @@ -179,6 +178,8 @@ static void samsung_keypad_start(struct samsung_keypad *keypad)  {  	unsigned int val; +	pm_runtime_get_sync(&keypad->pdev->dev); +  	/* Tell IRQ thread that it may poll the device. */  	keypad->stopped = false; @@ -191,12 +192,16 @@ static void samsung_keypad_start(struct samsung_keypad *keypad)  	/* KEYIFCOL reg clear. */  	writel(0, keypad->base + SAMSUNG_KEYIFCOL); + +	pm_runtime_put(&keypad->pdev->dev);  }  static void samsung_keypad_stop(struct samsung_keypad *keypad)  {  	unsigned int val; +	pm_runtime_get_sync(&keypad->pdev->dev); +  	/* Signal IRQ thread to stop polling and disable the handler. */  	keypad->stopped = true;  	wake_up(&keypad->wait); @@ -217,6 +222,8 @@ static void samsung_keypad_stop(struct samsung_keypad *keypad)  	 * re-enable the handler.  	 */  	enable_irq(keypad->irq); + +	pm_runtime_put(&keypad->pdev->dev);  }  static int samsung_keypad_open(struct input_dev *input_dev) @@ -235,7 +242,79 @@ static void samsung_keypad_close(struct input_dev *input_dev)  	samsung_keypad_stop(keypad);  } -static int __devinit samsung_keypad_probe(struct platform_device *pdev) +#ifdef CONFIG_OF +static struct samsung_keypad_platdata * +samsung_keypad_parse_dt(struct device *dev) +{ +	struct samsung_keypad_platdata *pdata; +	struct matrix_keymap_data *keymap_data; +	uint32_t *keymap, num_rows = 0, num_cols = 0; +	struct device_node *np = dev->of_node, *key_np; +	unsigned int key_count; + +	if (!np) { +		dev_err(dev, "missing device tree data\n"); +		return ERR_PTR(-EINVAL); +	} + +	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); +	if (!pdata) { +		dev_err(dev, "could not allocate memory for platform data\n"); +		return ERR_PTR(-ENOMEM); +	} + +	of_property_read_u32(np, "samsung,keypad-num-rows", &num_rows); +	of_property_read_u32(np, "samsung,keypad-num-columns", &num_cols); +	if (!num_rows || !num_cols) { +		dev_err(dev, "number of keypad rows/columns not specified\n"); +		return ERR_PTR(-EINVAL); +	} +	pdata->rows = num_rows; +	pdata->cols = num_cols; + +	keymap_data = devm_kzalloc(dev, sizeof(*keymap_data), GFP_KERNEL); +	if (!keymap_data) { +		dev_err(dev, "could not allocate memory for keymap data\n"); +		return ERR_PTR(-ENOMEM); +	} +	pdata->keymap_data = keymap_data; + +	key_count = of_get_child_count(np); +	keymap_data->keymap_size = key_count; +	keymap = devm_kzalloc(dev, sizeof(uint32_t) * key_count, GFP_KERNEL); +	if (!keymap) { +		dev_err(dev, "could not allocate memory for keymap\n"); +		return ERR_PTR(-ENOMEM); +	} +	keymap_data->keymap = keymap; + +	for_each_child_of_node(np, key_np) { +		u32 row, col, key_code; +		of_property_read_u32(key_np, "keypad,row", &row); +		of_property_read_u32(key_np, "keypad,column", &col); +		of_property_read_u32(key_np, "linux,code", &key_code); +		*keymap++ = KEY(row, col, key_code); +	} + +	if (of_get_property(np, "linux,input-no-autorepeat", NULL)) +		pdata->no_autorepeat = true; + +	if (of_get_property(np, "linux,input-wakeup", NULL)) +		pdata->wakeup = true; + +	return pdata; +} +#else +static struct samsung_keypad_platdata * +samsung_keypad_parse_dt(struct device *dev) +{ +	dev_err(dev, "no platform data defined\n"); + +	return ERR_PTR(-EINVAL); +} +#endif + +static int samsung_keypad_probe(struct platform_device *pdev)  {  	const struct samsung_keypad_platdata *pdata;  	const struct matrix_keymap_data *keymap_data; @@ -246,10 +325,11 @@ static int __devinit samsung_keypad_probe(struct platform_device *pdev)  	unsigned int keymap_size;  	int error; -	pdata = pdev->dev.platform_data; +	pdata = dev_get_platdata(&pdev->dev);  	if (!pdata) { -		dev_err(&pdev->dev, "no platform data defined\n"); -		return -EINVAL; +		pdata = samsung_keypad_parse_dt(&pdev->dev); +		if (IS_ERR(pdata)) +			return PTR_ERR(pdata);  	}  	keymap_data = pdata->keymap_data; @@ -271,121 +351,169 @@ static int __devinit samsung_keypad_probe(struct platform_device *pdev)  	row_shift = get_count_order(pdata->cols);  	keymap_size = (pdata->rows << row_shift) * sizeof(keypad->keycodes[0]); -	keypad = kzalloc(sizeof(*keypad) + keymap_size, GFP_KERNEL); -	input_dev = input_allocate_device(); -	if (!keypad || !input_dev) { -		error = -ENOMEM; -		goto err_free_mem; -	} +	keypad = devm_kzalloc(&pdev->dev, sizeof(*keypad) + keymap_size, +			      GFP_KERNEL); +	input_dev = devm_input_allocate_device(&pdev->dev); +	if (!keypad || !input_dev) +		return -ENOMEM;  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -	if (!res) { -		error = -ENODEV; -		goto err_free_mem; -	} +	if (!res) +		return -ENODEV; -	keypad->base = ioremap(res->start, resource_size(res)); -	if (!keypad->base) { -		error = -EBUSY; -		goto err_free_mem; -	} +	keypad->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); +	if (!keypad->base) +		return -EBUSY; -	keypad->clk = clk_get(&pdev->dev, "keypad"); +	keypad->clk = devm_clk_get(&pdev->dev, "keypad");  	if (IS_ERR(keypad->clk)) {  		dev_err(&pdev->dev, "failed to get keypad clk\n"); -		error = PTR_ERR(keypad->clk); -		goto err_unmap_base; +		return PTR_ERR(keypad->clk); +	} + +	error = clk_prepare(keypad->clk); +	if (error) { +		dev_err(&pdev->dev, "keypad clock prepare failed\n"); +		return error;  	}  	keypad->input_dev = input_dev; +	keypad->pdev = pdev;  	keypad->row_shift = row_shift;  	keypad->rows = pdata->rows;  	keypad->cols = pdata->cols; +	keypad->stopped = true;  	init_waitqueue_head(&keypad->wait); +	if (pdev->dev.of_node) +		keypad->type = of_device_is_compatible(pdev->dev.of_node, +					"samsung,s5pv210-keypad"); +	else +		keypad->type = platform_get_device_id(pdev)->driver_data; +  	input_dev->name = pdev->name;  	input_dev->id.bustype = BUS_HOST;  	input_dev->dev.parent = &pdev->dev; -	input_set_drvdata(input_dev, keypad);  	input_dev->open = samsung_keypad_open;  	input_dev->close = samsung_keypad_close; -	input_dev->evbit[0] = BIT_MASK(EV_KEY); -	if (!pdata->no_autorepeat) -		input_dev->evbit[0] |= BIT_MASK(EV_REP); +	error = matrix_keypad_build_keymap(keymap_data, NULL, +					   pdata->rows, pdata->cols, +					   keypad->keycodes, input_dev); +	if (error) { +		dev_err(&pdev->dev, "failed to build keymap\n"); +		goto err_unprepare_clk; +	}  	input_set_capability(input_dev, EV_MSC, MSC_SCAN); +	if (!pdata->no_autorepeat) +		__set_bit(EV_REP, input_dev->evbit); -	input_dev->keycode = keypad->keycodes; -	input_dev->keycodesize = sizeof(keypad->keycodes[0]); -	input_dev->keycodemax = pdata->rows << row_shift; - -	matrix_keypad_build_keymap(keymap_data, row_shift, -			input_dev->keycode, input_dev->keybit); +	input_set_drvdata(input_dev, keypad);  	keypad->irq = platform_get_irq(pdev, 0);  	if (keypad->irq < 0) {  		error = keypad->irq; -		goto err_put_clk; +		goto err_unprepare_clk;  	} -	error = request_threaded_irq(keypad->irq, NULL, samsung_keypad_irq, -			IRQF_ONESHOT, dev_name(&pdev->dev), keypad); +	error = devm_request_threaded_irq(&pdev->dev, keypad->irq, NULL, +					  samsung_keypad_irq, IRQF_ONESHOT, +					  dev_name(&pdev->dev), keypad);  	if (error) {  		dev_err(&pdev->dev, "failed to register keypad interrupt\n"); -		goto err_put_clk; +		goto err_unprepare_clk;  	} +	device_init_wakeup(&pdev->dev, pdata->wakeup); +	platform_set_drvdata(pdev, keypad); +	pm_runtime_enable(&pdev->dev); +  	error = input_register_device(keypad->input_dev);  	if (error) -		goto err_free_irq; +		goto err_disable_runtime_pm; -	device_init_wakeup(&pdev->dev, pdata->wakeup); -	platform_set_drvdata(pdev, keypad); +	if (pdev->dev.of_node) { +		devm_kfree(&pdev->dev, (void *)pdata->keymap_data->keymap); +		devm_kfree(&pdev->dev, (void *)pdata->keymap_data); +		devm_kfree(&pdev->dev, (void *)pdata); +	}  	return 0; -err_free_irq: -	free_irq(keypad->irq, keypad); -err_put_clk: -	clk_put(keypad->clk); -err_unmap_base: -	iounmap(keypad->base); -err_free_mem: -	input_free_device(input_dev); -	kfree(keypad); - +err_disable_runtime_pm: +	pm_runtime_disable(&pdev->dev); +	device_init_wakeup(&pdev->dev, 0); +err_unprepare_clk: +	clk_unprepare(keypad->clk);  	return error;  } -static int __devexit samsung_keypad_remove(struct platform_device *pdev) +static int samsung_keypad_remove(struct platform_device *pdev)  {  	struct samsung_keypad *keypad = platform_get_drvdata(pdev); +	pm_runtime_disable(&pdev->dev);  	device_init_wakeup(&pdev->dev, 0); -	platform_set_drvdata(pdev, NULL);  	input_unregister_device(keypad->input_dev); -	/* -	 * It is safe to free IRQ after unregistering device because -	 * samsung_keypad_close will shut off interrupts. -	 */ -	free_irq(keypad->irq, keypad); +	clk_unprepare(keypad->clk); -	clk_put(keypad->clk); +	return 0; +} -	iounmap(keypad->base); -	kfree(keypad); +#ifdef CONFIG_PM_RUNTIME +static int samsung_keypad_runtime_suspend(struct device *dev) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct samsung_keypad *keypad = platform_get_drvdata(pdev); +	unsigned int val; +	int error; + +	if (keypad->stopped) +		return 0; + +	/* This may fail on some SoCs due to lack of controller support */ +	error = enable_irq_wake(keypad->irq); +	if (!error) +		keypad->wake_enabled = true; + +	val = readl(keypad->base + SAMSUNG_KEYIFCON); +	val |= SAMSUNG_KEYIFCON_WAKEUPEN; +	writel(val, keypad->base + SAMSUNG_KEYIFCON); + +	clk_disable(keypad->clk);  	return 0;  } -#ifdef CONFIG_PM +static int samsung_keypad_runtime_resume(struct device *dev) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct samsung_keypad *keypad = platform_get_drvdata(pdev); +	unsigned int val; + +	if (keypad->stopped) +		return 0; + +	clk_enable(keypad->clk); + +	val = readl(keypad->base + SAMSUNG_KEYIFCON); +	val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; +	writel(val, keypad->base + SAMSUNG_KEYIFCON); + +	if (keypad->wake_enabled) +		disable_irq_wake(keypad->irq); + +	return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP  static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad,  					 bool enable)  { -	struct device *dev = keypad->input_dev->dev.parent;  	unsigned int val;  	clk_enable(keypad->clk); @@ -393,11 +521,11 @@ static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad,  	val = readl(keypad->base + SAMSUNG_KEYIFCON);  	if (enable) {  		val |= SAMSUNG_KEYIFCON_WAKEUPEN; -		if (device_may_wakeup(dev)) +		if (device_may_wakeup(&keypad->pdev->dev))  			enable_irq_wake(keypad->irq);  	} else {  		val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; -		if (device_may_wakeup(dev)) +		if (device_may_wakeup(&keypad->pdev->dev))  			disable_irq_wake(keypad->irq);  	}  	writel(val, keypad->base + SAMSUNG_KEYIFCON); @@ -440,11 +568,21 @@ static int samsung_keypad_resume(struct device *dev)  	return 0;  } +#endif  static const struct dev_pm_ops samsung_keypad_pm_ops = { -	.suspend	= samsung_keypad_suspend, -	.resume		= samsung_keypad_resume, +	SET_SYSTEM_SLEEP_PM_OPS(samsung_keypad_suspend, samsung_keypad_resume) +	SET_RUNTIME_PM_OPS(samsung_keypad_runtime_suspend, +			   samsung_keypad_runtime_resume, NULL)  }; + +#ifdef CONFIG_OF +static const struct of_device_id samsung_keypad_dt_match[] = { +	{ .compatible = "samsung,s3c6410-keypad" }, +	{ .compatible = "samsung,s5pv210-keypad" }, +	{}, +}; +MODULE_DEVICE_TABLE(of, samsung_keypad_dt_match);  #endif  static struct platform_device_id samsung_keypad_driver_ids[] = { @@ -461,31 +599,18 @@ MODULE_DEVICE_TABLE(platform, samsung_keypad_driver_ids);  static struct platform_driver samsung_keypad_driver = {  	.probe		= samsung_keypad_probe, -	.remove		= __devexit_p(samsung_keypad_remove), +	.remove		= samsung_keypad_remove,  	.driver		= {  		.name	= "samsung-keypad",  		.owner	= THIS_MODULE, -#ifdef CONFIG_PM +		.of_match_table = of_match_ptr(samsung_keypad_dt_match),  		.pm	= &samsung_keypad_pm_ops, -#endif  	},  	.id_table	= samsung_keypad_driver_ids,  }; - -static int __init samsung_keypad_init(void) -{ -	return platform_driver_register(&samsung_keypad_driver); -} -module_init(samsung_keypad_init); - -static void __exit samsung_keypad_exit(void) -{ -	platform_driver_unregister(&samsung_keypad_driver); -} -module_exit(samsung_keypad_exit); +module_platform_driver(samsung_keypad_driver);  MODULE_DESCRIPTION("Samsung keypad driver");  MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");  MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>");  MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:samsung-keypad");  | 
