281 lines
6.9 KiB
C
281 lines
6.9 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright (c) 2019 MediaTek Inc.
|
||
|
*/
|
||
|
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/dma-mapping.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/regulator/consumer.h>
|
||
|
#include <media/rc-core.h>
|
||
|
|
||
|
#include <mt-plat/mtk_pwm.h>
|
||
|
#include <mach/mtk_pwm_hal.h>
|
||
|
|
||
|
#define DRIVER_NAME "mtk-pwm-ir-tx"
|
||
|
#define DEVICE_NAME "MTK PWM IR Transmitter"
|
||
|
#define IRTX_PWM_CLOCK (26000000)
|
||
|
//#define IRTX_DEBUG
|
||
|
|
||
|
struct mtk_pwm_ir {
|
||
|
struct regulator *regulator;
|
||
|
unsigned int pwm_ch;
|
||
|
unsigned int pwm_data_invert;
|
||
|
unsigned int carrier;
|
||
|
unsigned int duty_cycle;
|
||
|
unsigned int cycle;
|
||
|
struct platform_device *pdev;
|
||
|
};
|
||
|
|
||
|
static struct pwm_spec_config irtx_pwm_config = {
|
||
|
.pwm_no = 0,
|
||
|
.mode = PWM_MODE_MEMORY,
|
||
|
.clk_div = CLK_DIV1,
|
||
|
.clk_src = PWM_CLK_NEW_MODE_BLOCK,
|
||
|
.pmic_pad = 0,
|
||
|
.PWM_MODE_MEMORY_REGS.IDLE_VALUE = IDLE_FALSE,
|
||
|
.PWM_MODE_MEMORY_REGS.GUARD_VALUE = GUARD_FALSE,
|
||
|
.PWM_MODE_MEMORY_REGS.STOP_BITPOS_VALUE = 31,
|
||
|
/* 1 microseconds, assume clock source is 26M */
|
||
|
.PWM_MODE_MEMORY_REGS.HDURATION = 229,
|
||
|
.PWM_MODE_MEMORY_REGS.LDURATION = 229,
|
||
|
.PWM_MODE_MEMORY_REGS.GDURATION = 0,
|
||
|
.PWM_MODE_MEMORY_REGS.WAVE_NUM = 1,
|
||
|
};
|
||
|
|
||
|
static int mtk_pwm_ir_tx(struct rc_dev *rcdev, unsigned int *txbuf,
|
||
|
unsigned int count)
|
||
|
{
|
||
|
struct mtk_pwm_ir *pwm_ir = rcdev->priv;
|
||
|
dma_addr_t wave_phy;
|
||
|
unsigned int *wave_vir;
|
||
|
int ret, i, h_l_period, cycle_unit_us;
|
||
|
int buf_size = 0;
|
||
|
int total_time = 0;
|
||
|
int len = 0;
|
||
|
int cur_bit = 0;
|
||
|
int regulator_enabled = 0;
|
||
|
#ifdef IRTX_DEBUG
|
||
|
static char logbuf[4096];
|
||
|
int cur_idx = 0;
|
||
|
char *dbglog = logbuf;
|
||
|
#endif
|
||
|
|
||
|
pr_info("%s() irtx len=0x%x, pwm=%d\n", __func__,
|
||
|
(unsigned int)count, (unsigned int)pwm_ir->pwm_ch);
|
||
|
|
||
|
/* lirc txbuf is odd, consumerir will append a "1" at last
|
||
|
* if original pattern_len is even.
|
||
|
*/
|
||
|
if ((count > 0) && (txbuf[count-1] == 1))
|
||
|
count--;
|
||
|
|
||
|
if (count == 0)
|
||
|
return 0;
|
||
|
|
||
|
// pwm_ir.cycle: whole cycle, pwm_ir.duty_cycle: high period
|
||
|
h_l_period = DIV_ROUND_UP(IRTX_PWM_CLOCK*pwm_ir->duty_cycle,
|
||
|
pwm_ir->carrier*pwm_ir->cycle);
|
||
|
cycle_unit_us = DIV_ROUND_UP(NSEC_PER_SEC/1000*pwm_ir->duty_cycle,
|
||
|
pwm_ir->carrier*pwm_ir->cycle);
|
||
|
|
||
|
for (i = 0; i < count; i++) {
|
||
|
buf_size += ALIGN(DIV_ROUND_UP(txbuf[i], cycle_unit_us), pwm_ir->cycle);
|
||
|
total_time += txbuf[i];
|
||
|
}
|
||
|
|
||
|
buf_size = ALIGN(buf_size, BITS_PER_BYTE * sizeof(unsigned int));
|
||
|
buf_size = buf_size / BITS_PER_BYTE; /* byte size */
|
||
|
|
||
|
wave_vir = (unsigned int *) dma_alloc_coherent(&pwm_ir->pdev->dev, buf_size,
|
||
|
&wave_phy, GFP_KERNEL);
|
||
|
if (!wave_vir) {
|
||
|
pr_notice("%s() IRTX alloc memory fail\n", __func__);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
memset(wave_vir, 0, buf_size);
|
||
|
/* convert the pulse/space signal to raw binary signal */
|
||
|
cur_bit = 0;
|
||
|
for (i = 0; i < count; i++) {
|
||
|
unsigned int periods;
|
||
|
int j, cur_cycle = 0;
|
||
|
|
||
|
periods = ALIGN(DIV_ROUND_UP(txbuf[i], cycle_unit_us), pwm_ir->cycle);
|
||
|
|
||
|
for (j = 0; j < periods; j++) {
|
||
|
cur_cycle = (j % pwm_ir->cycle)+1;
|
||
|
if (cur_cycle > pwm_ir->duty_cycle || (i % 2)) {
|
||
|
if (pwm_ir->pwm_data_invert)
|
||
|
wave_vir[len] |= (1 << cur_bit);
|
||
|
else
|
||
|
wave_vir[len] &= ~(1 << cur_bit);
|
||
|
} else {
|
||
|
if (pwm_ir->pwm_data_invert)
|
||
|
wave_vir[len] &= ~(1 << cur_bit);
|
||
|
else
|
||
|
wave_vir[len] |= (1 << cur_bit);
|
||
|
}
|
||
|
cur_bit++;
|
||
|
if (cur_bit == 32) {
|
||
|
cur_bit = 0;
|
||
|
len++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (cur_bit > 0)
|
||
|
len++;
|
||
|
|
||
|
irtx_pwm_config.pwm_no = (unsigned int)pwm_ir->pwm_ch;
|
||
|
irtx_pwm_config.PWM_MODE_MEMORY_REGS.HDURATION = h_l_period-1;
|
||
|
irtx_pwm_config.PWM_MODE_MEMORY_REGS.LDURATION = h_l_period-1;
|
||
|
irtx_pwm_config.PWM_MODE_MEMORY_REGS.BUF0_BASE_ADDR = wave_phy;
|
||
|
irtx_pwm_config.PWM_MODE_MEMORY_REGS.BUF0_SIZE = len;
|
||
|
|
||
|
#ifdef IRTX_DEBUG
|
||
|
dbglog = logbuf;
|
||
|
pr_info("h_l_period = %d, cycle_unit_us = %d\n",
|
||
|
h_l_period, cycle_unit_us);
|
||
|
pr_info("irtx len = %d, buf_size = %d, total_time = %d\n",
|
||
|
len, buf_size, total_time);
|
||
|
for (i = 0; i < len; i++) {
|
||
|
if (i && (i % 16 == 0)) {
|
||
|
pr_info("[%d] %s\n", cur_idx++, logbuf);
|
||
|
memset(logbuf, 0, sizeof(logbuf));
|
||
|
dbglog = logbuf;
|
||
|
}
|
||
|
dbglog += sprintf(dbglog, "0x%08x ", wave_vir[i]);
|
||
|
}
|
||
|
if (dbglog != logbuf)
|
||
|
pr_info("[%d] %s\n", cur_idx++, logbuf);
|
||
|
#endif
|
||
|
|
||
|
if (pwm_ir->regulator != NULL) {
|
||
|
if (!regulator_is_enabled(pwm_ir->regulator)) {
|
||
|
ret = regulator_enable(pwm_ir->regulator);
|
||
|
if (ret < 0) {
|
||
|
pr_err("%s:%d regulator_enable fail!\n",
|
||
|
__func__, __LINE__);
|
||
|
goto exit_free;
|
||
|
} else {
|
||
|
regulator_enabled = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ret = pwm_set_spec_config(&irtx_pwm_config);
|
||
|
if (ret < 0) {
|
||
|
pr_err("pwm_set_spec_config fail, ret: %d\n", ret);
|
||
|
goto exit_free;
|
||
|
}
|
||
|
|
||
|
usleep_range(total_time, total_time + 100);
|
||
|
|
||
|
pr_info("[IRTX] done, clean up\n");
|
||
|
mt_pwm_disable(irtx_pwm_config.pwm_no, irtx_pwm_config.pmic_pad);
|
||
|
|
||
|
if (pwm_ir->regulator != NULL) {
|
||
|
if (regulator_enabled && regulator_is_enabled(pwm_ir->regulator)) {
|
||
|
ret = regulator_disable(pwm_ir->regulator);
|
||
|
if (ret < 0) {
|
||
|
pr_err("%s:%d regulator_disable fail!\n",
|
||
|
__func__, __LINE__);
|
||
|
goto exit_free;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ret = count;
|
||
|
|
||
|
exit_free:
|
||
|
dma_free_coherent(&pwm_ir->pdev->dev, buf_size, wave_vir, wave_phy);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int mtk_pwm_ir_set_duty_cycle(struct rc_dev *dev, u32 duty_cycle)
|
||
|
{
|
||
|
struct mtk_pwm_ir *pwm_ir = dev->priv;
|
||
|
|
||
|
if (duty_cycle < 40) {
|
||
|
pwm_ir->cycle = 3;
|
||
|
pwm_ir->duty_cycle = 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int mtk_pwm_ir_set_carrier(struct rc_dev *dev, u32 carrier)
|
||
|
{
|
||
|
struct mtk_pwm_ir *pwm_ir = dev->priv;
|
||
|
|
||
|
if (!carrier)
|
||
|
return -EINVAL;
|
||
|
|
||
|
pwm_ir->carrier = carrier;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int mtk_pwm_ir_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct mtk_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;
|
||
|
|
||
|
of_property_read_u32(pdev->dev.of_node, "pwm_ch",
|
||
|
&pwm_ir->pwm_ch);
|
||
|
of_property_read_u32(pdev->dev.of_node, "pwm_data_invert",
|
||
|
&pwm_ir->pwm_data_invert);
|
||
|
|
||
|
pwm_ir->regulator = devm_regulator_get(&pdev->dev, "vio28");
|
||
|
if (IS_ERR(pwm_ir->regulator))
|
||
|
return PTR_ERR(pwm_ir->regulator);
|
||
|
|
||
|
pwm_ir->pdev = pdev;
|
||
|
|
||
|
rc = regulator_set_voltage(pwm_ir->regulator, 2800000, 2800000);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
|
||
|
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 = mtk_pwm_ir_tx;
|
||
|
rcdev->s_tx_duty_cycle = mtk_pwm_ir_set_duty_cycle;
|
||
|
rcdev->s_tx_carrier = mtk_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 const struct of_device_id mtk_pwm_ir_of_match[] = {
|
||
|
{.compatible = "mediatek,irtx-pwm",},
|
||
|
{}
|
||
|
};
|
||
|
|
||
|
static struct platform_driver pwm_ir_driver = {
|
||
|
.probe = mtk_pwm_ir_probe,
|
||
|
.driver = {
|
||
|
.name = DRIVER_NAME,
|
||
|
.of_match_table = of_match_ptr(mtk_pwm_ir_of_match),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
module_platform_driver(pwm_ir_driver);
|
||
|
|
||
|
MODULE_DESCRIPTION("MTK PWM IR Transmitter");
|
||
|
MODULE_AUTHOR("Chang-An Chen <chang-an.chen@mediatek.com>");
|
||
|
MODULE_LICENSE("GPL");
|