18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci// SPI driven IR LED device driver 38c2ecf20Sopenharmony_ci// 48c2ecf20Sopenharmony_ci// Copyright (c) 2016 Samsung Electronics Co., Ltd. 58c2ecf20Sopenharmony_ci// Copyright (c) Andi Shyti <andi@etezian.org> 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/delay.h> 88c2ecf20Sopenharmony_ci#include <linux/fs.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/mutex.h> 118c2ecf20Sopenharmony_ci#include <linux/of_gpio.h> 128c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h> 138c2ecf20Sopenharmony_ci#include <linux/spi/spi.h> 148c2ecf20Sopenharmony_ci#include <media/rc-core.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define IR_SPI_DRIVER_NAME "ir-spi" 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define IR_SPI_DEFAULT_FREQUENCY 38000 198c2ecf20Sopenharmony_ci#define IR_SPI_MAX_BUFSIZE 4096 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistruct ir_spi_data { 228c2ecf20Sopenharmony_ci u32 freq; 238c2ecf20Sopenharmony_ci bool negated; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci u16 tx_buf[IR_SPI_MAX_BUFSIZE]; 268c2ecf20Sopenharmony_ci u16 pulse; 278c2ecf20Sopenharmony_ci u16 space; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci struct rc_dev *rc; 308c2ecf20Sopenharmony_ci struct spi_device *spi; 318c2ecf20Sopenharmony_ci struct regulator *regulator; 328c2ecf20Sopenharmony_ci}; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic int ir_spi_tx(struct rc_dev *dev, 358c2ecf20Sopenharmony_ci unsigned int *buffer, unsigned int count) 368c2ecf20Sopenharmony_ci{ 378c2ecf20Sopenharmony_ci int i; 388c2ecf20Sopenharmony_ci int ret; 398c2ecf20Sopenharmony_ci unsigned int len = 0; 408c2ecf20Sopenharmony_ci struct ir_spi_data *idata = dev->priv; 418c2ecf20Sopenharmony_ci struct spi_transfer xfer; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci /* convert the pulse/space signal to raw binary signal */ 448c2ecf20Sopenharmony_ci for (i = 0; i < count; i++) { 458c2ecf20Sopenharmony_ci unsigned int periods; 468c2ecf20Sopenharmony_ci int j; 478c2ecf20Sopenharmony_ci u16 val; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci periods = DIV_ROUND_CLOSEST(buffer[i] * idata->freq, 1000000); 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci if (len + periods >= IR_SPI_MAX_BUFSIZE) 528c2ecf20Sopenharmony_ci return -EINVAL; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci /* 558c2ecf20Sopenharmony_ci * the first value in buffer is a pulse, so that 0, 2, 4, ... 568c2ecf20Sopenharmony_ci * contain a pulse duration. On the contrary, 1, 3, 5, ... 578c2ecf20Sopenharmony_ci * contain a space duration. 588c2ecf20Sopenharmony_ci */ 598c2ecf20Sopenharmony_ci val = (i % 2) ? idata->space : idata->pulse; 608c2ecf20Sopenharmony_ci for (j = 0; j < periods; j++) 618c2ecf20Sopenharmony_ci idata->tx_buf[len++] = val; 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci memset(&xfer, 0, sizeof(xfer)); 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci xfer.speed_hz = idata->freq * 16; 678c2ecf20Sopenharmony_ci xfer.len = len * sizeof(*idata->tx_buf); 688c2ecf20Sopenharmony_ci xfer.tx_buf = idata->tx_buf; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci ret = regulator_enable(idata->regulator); 718c2ecf20Sopenharmony_ci if (ret) 728c2ecf20Sopenharmony_ci return ret; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci ret = spi_sync_transfer(idata->spi, &xfer, 1); 758c2ecf20Sopenharmony_ci if (ret) 768c2ecf20Sopenharmony_ci dev_err(&idata->spi->dev, "unable to deliver the signal\n"); 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci regulator_disable(idata->regulator); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci return ret ? ret : count; 818c2ecf20Sopenharmony_ci} 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_cistatic int ir_spi_set_tx_carrier(struct rc_dev *dev, u32 carrier) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci struct ir_spi_data *idata = dev->priv; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (!carrier) 888c2ecf20Sopenharmony_ci return -EINVAL; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci idata->freq = carrier; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci return 0; 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic int ir_spi_set_duty_cycle(struct rc_dev *dev, u32 duty_cycle) 968c2ecf20Sopenharmony_ci{ 978c2ecf20Sopenharmony_ci struct ir_spi_data *idata = dev->priv; 988c2ecf20Sopenharmony_ci int bits = (duty_cycle * 15) / 100; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci idata->pulse = GENMASK(bits, 0); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci if (idata->negated) { 1038c2ecf20Sopenharmony_ci idata->pulse = ~idata->pulse; 1048c2ecf20Sopenharmony_ci idata->space = 0xffff; 1058c2ecf20Sopenharmony_ci } else { 1068c2ecf20Sopenharmony_ci idata->space = 0; 1078c2ecf20Sopenharmony_ci } 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci return 0; 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic int ir_spi_probe(struct spi_device *spi) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci int ret; 1158c2ecf20Sopenharmony_ci u8 dc; 1168c2ecf20Sopenharmony_ci struct ir_spi_data *idata; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci idata = devm_kzalloc(&spi->dev, sizeof(*idata), GFP_KERNEL); 1198c2ecf20Sopenharmony_ci if (!idata) 1208c2ecf20Sopenharmony_ci return -ENOMEM; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci idata->regulator = devm_regulator_get(&spi->dev, "irda_regulator"); 1238c2ecf20Sopenharmony_ci if (IS_ERR(idata->regulator)) 1248c2ecf20Sopenharmony_ci return PTR_ERR(idata->regulator); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci idata->rc = devm_rc_allocate_device(&spi->dev, RC_DRIVER_IR_RAW_TX); 1278c2ecf20Sopenharmony_ci if (!idata->rc) 1288c2ecf20Sopenharmony_ci return -ENOMEM; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci idata->rc->tx_ir = ir_spi_tx; 1318c2ecf20Sopenharmony_ci idata->rc->s_tx_carrier = ir_spi_set_tx_carrier; 1328c2ecf20Sopenharmony_ci idata->rc->s_tx_duty_cycle = ir_spi_set_duty_cycle; 1338c2ecf20Sopenharmony_ci idata->rc->device_name = "IR SPI"; 1348c2ecf20Sopenharmony_ci idata->rc->driver_name = IR_SPI_DRIVER_NAME; 1358c2ecf20Sopenharmony_ci idata->rc->priv = idata; 1368c2ecf20Sopenharmony_ci idata->spi = spi; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci idata->negated = of_property_read_bool(spi->dev.of_node, 1398c2ecf20Sopenharmony_ci "led-active-low"); 1408c2ecf20Sopenharmony_ci ret = of_property_read_u8(spi->dev.of_node, "duty-cycle", &dc); 1418c2ecf20Sopenharmony_ci if (ret) 1428c2ecf20Sopenharmony_ci dc = 50; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci /* ir_spi_set_duty_cycle cannot fail, 1458c2ecf20Sopenharmony_ci * it returns int to be compatible with the 1468c2ecf20Sopenharmony_ci * rc->s_tx_duty_cycle function 1478c2ecf20Sopenharmony_ci */ 1488c2ecf20Sopenharmony_ci ir_spi_set_duty_cycle(idata->rc, dc); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci idata->freq = IR_SPI_DEFAULT_FREQUENCY; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci return devm_rc_register_device(&spi->dev, idata->rc); 1538c2ecf20Sopenharmony_ci} 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cistatic int ir_spi_remove(struct spi_device *spi) 1568c2ecf20Sopenharmony_ci{ 1578c2ecf20Sopenharmony_ci return 0; 1588c2ecf20Sopenharmony_ci} 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_cistatic const struct of_device_id ir_spi_of_match[] = { 1618c2ecf20Sopenharmony_ci { .compatible = "ir-spi-led" }, 1628c2ecf20Sopenharmony_ci {}, 1638c2ecf20Sopenharmony_ci}; 1648c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ir_spi_of_match); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic struct spi_driver ir_spi_driver = { 1678c2ecf20Sopenharmony_ci .probe = ir_spi_probe, 1688c2ecf20Sopenharmony_ci .remove = ir_spi_remove, 1698c2ecf20Sopenharmony_ci .driver = { 1708c2ecf20Sopenharmony_ci .name = IR_SPI_DRIVER_NAME, 1718c2ecf20Sopenharmony_ci .of_match_table = ir_spi_of_match, 1728c2ecf20Sopenharmony_ci }, 1738c2ecf20Sopenharmony_ci}; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_cimodule_spi_driver(ir_spi_driver); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andi Shyti <andi@etezian.org>"); 1788c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SPI IR LED"); 1798c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 180