| /* | 
 |  * Copyright (C) 2017 Sean Young <sean@mess.org> | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License version 2, or | 
 |  * (at your option) any later version. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  * GNU General Public License for more details. | 
 |  */ | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/module.h> | 
 | #include <linux/pwm.h> | 
 | #include <linux/delay.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/of.h> | 
 | #include <linux/platform_device.h> | 
 | #include <media/rc-core.h> | 
 |  | 
 | #define DRIVER_NAME	"pwm-ir-tx" | 
 | #define DEVICE_NAME	"PWM IR Transmitter" | 
 |  | 
 | struct pwm_ir { | 
 | 	struct pwm_device *pwm; | 
 | 	unsigned int carrier; | 
 | 	unsigned int duty_cycle; | 
 | }; | 
 |  | 
 | static const struct of_device_id pwm_ir_of_match[] = { | 
 | 	{ .compatible = "pwm-ir-tx", }, | 
 | 	{ }, | 
 | }; | 
 | MODULE_DEVICE_TABLE(of, pwm_ir_of_match); | 
 |  | 
 | static int pwm_ir_set_duty_cycle(struct rc_dev *dev, u32 duty_cycle) | 
 | { | 
 | 	struct pwm_ir *pwm_ir = dev->priv; | 
 |  | 
 | 	pwm_ir->duty_cycle = duty_cycle; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int pwm_ir_set_carrier(struct rc_dev *dev, u32 carrier) | 
 | { | 
 | 	struct pwm_ir *pwm_ir = dev->priv; | 
 |  | 
 | 	if (!carrier) | 
 | 		return -EINVAL; | 
 |  | 
 | 	pwm_ir->carrier = carrier; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int pwm_ir_tx(struct rc_dev *dev, unsigned int *txbuf, | 
 | 		     unsigned int count) | 
 | { | 
 | 	struct pwm_ir *pwm_ir = dev->priv; | 
 | 	struct pwm_device *pwm = pwm_ir->pwm; | 
 | 	int i, duty, period; | 
 | 	ktime_t edge; | 
 | 	long delta; | 
 |  | 
 | 	period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, pwm_ir->carrier); | 
 | 	duty = DIV_ROUND_CLOSEST(pwm_ir->duty_cycle * period, 100); | 
 |  | 
 | 	pwm_config(pwm, duty, period); | 
 |  | 
 | 	edge = ktime_get(); | 
 |  | 
 | 	for (i = 0; i < count; i++) { | 
 | 		if (i % 2) // space | 
 | 			pwm_disable(pwm); | 
 | 		else | 
 | 			pwm_enable(pwm); | 
 |  | 
 | 		edge = ktime_add_us(edge, txbuf[i]); | 
 | 		delta = ktime_us_delta(edge, ktime_get()); | 
 | 		if (delta > 0) | 
 | 			usleep_range(delta, delta + 10); | 
 | 	} | 
 |  | 
 | 	pwm_disable(pwm); | 
 |  | 
 | 	return count; | 
 | } | 
 |  | 
 | static int pwm_ir_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct pwm_ir *pwm_ir; | 
 | 	struct rc_dev *rcdev; | 
 | 	int rc; | 
 |  | 
 | 	pwm_ir = devm_kmalloc(&pdev->dev, sizeof(*pwm_ir), GFP_KERNEL); | 
 | 	if (!pwm_ir) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	pwm_ir->pwm = devm_pwm_get(&pdev->dev, NULL); | 
 | 	if (IS_ERR(pwm_ir->pwm)) | 
 | 		return PTR_ERR(pwm_ir->pwm); | 
 |  | 
 | 	pwm_ir->carrier = 38000; | 
 | 	pwm_ir->duty_cycle = 50; | 
 |  | 
 | 	rcdev = devm_rc_allocate_device(&pdev->dev, RC_DRIVER_IR_RAW_TX); | 
 | 	if (!rcdev) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	rcdev->priv = pwm_ir; | 
 | 	rcdev->driver_name = DRIVER_NAME; | 
 | 	rcdev->device_name = DEVICE_NAME; | 
 | 	rcdev->tx_ir = pwm_ir_tx; | 
 | 	rcdev->s_tx_duty_cycle = pwm_ir_set_duty_cycle; | 
 | 	rcdev->s_tx_carrier = pwm_ir_set_carrier; | 
 |  | 
 | 	rc = devm_rc_register_device(&pdev->dev, rcdev); | 
 | 	if (rc < 0) | 
 | 		dev_err(&pdev->dev, "failed to register rc device\n"); | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | static struct platform_driver pwm_ir_driver = { | 
 | 	.probe = pwm_ir_probe, | 
 | 	.driver = { | 
 | 		.name	= DRIVER_NAME, | 
 | 		.of_match_table = of_match_ptr(pwm_ir_of_match), | 
 | 	}, | 
 | }; | 
 | module_platform_driver(pwm_ir_driver); | 
 |  | 
 | MODULE_DESCRIPTION("PWM IR Transmitter"); | 
 | MODULE_AUTHOR("Sean Young <sean@mess.org>"); | 
 | MODULE_LICENSE("GPL"); |