brintos

brintos / linux-shallow public Read only

0
0
Text · 17.2 KiB · 5d4c5a2 Raw
789 lines · c
1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause2/*3 * CEC driver for SECO X86 Boards4 *5 * Author:  Ettore Chimenti <ek5.chimenti@gmail.com>6 * Copyright (C) 2018, SECO SpA.7 * Copyright (C) 2018, Aidilab Srl.8 */9 10#include <linux/module.h>11#include <linux/acpi.h>12#include <linux/delay.h>13#include <linux/dmi.h>14#include <linux/gpio/consumer.h>15#include <linux/interrupt.h>16#include <linux/pci.h>17#include <linux/platform_device.h>18 19/* CEC Framework */20#include <media/cec-notifier.h>21 22#include "seco-cec.h"23 24struct secocec_data {25	struct device *dev;26	struct platform_device *pdev;27	struct cec_adapter *cec_adap;28	struct cec_notifier *notifier;29	struct rc_dev *ir;30	char ir_input_phys[32];31	int irq;32};33 34#define smb_wr16(cmd, data) smb_word_op(SECOCEC_MICRO_ADDRESS, \35					cmd, data, SMBUS_WRITE, NULL)36#define smb_rd16(cmd, res) smb_word_op(SECOCEC_MICRO_ADDRESS, \37				       cmd, 0, SMBUS_READ, res)38 39static int smb_word_op(u16 slave_addr, u8 cmd, u16 data,40		       u8 operation, u16 *result)41{42	unsigned int count;43	int status = 0;44 45	/* Active wait until ready */46	for (count = 0; count <= SMBTIMEOUT; ++count) {47		if (!(inb(HSTS) & BRA_INUSE_STS))48			break;49		udelay(SMB_POLL_UDELAY);50	}51 52	if (count > SMBTIMEOUT)53		/* Reset the lock instead of failing */54		outb(0xff, HSTS);55 56	outb(0x00, HCNT);57	outb((u8)(slave_addr & 0xfe) | operation, XMIT_SLVA);58	outb(cmd, HCMD);59	inb(HCNT);60 61	if (operation == SMBUS_WRITE) {62		outb((u8)data, HDAT0);63		outb((u8)(data >> 8), HDAT1);64	}65 66	outb(BRA_START + BRA_SMB_CMD_WORD_DATA, HCNT);67 68	for (count = 0; count <= SMBTIMEOUT; count++) {69		if (!(inb(HSTS) & BRA_HOST_BUSY))70			break;71		udelay(SMB_POLL_UDELAY);72	}73 74	if (count > SMBTIMEOUT) {75		status = -EBUSY;76		goto err;77	}78 79	if (inb(HSTS) & BRA_HSTS_ERR_MASK) {80		status = -EIO;81		goto err;82	}83 84	if (operation == SMBUS_READ)85		*result = ((inb(HDAT0) & 0xff) + ((inb(HDAT1) & 0xff) << 8));86 87err:88	outb(0xff, HSTS);89	return status;90}91 92static int secocec_adap_enable(struct cec_adapter *adap, bool enable)93{94	struct secocec_data *cec = cec_get_drvdata(adap);95	struct device *dev = cec->dev;96	u16 val = 0;97	int status;98 99	if (enable) {100		/* Clear the status register */101		status = smb_rd16(SECOCEC_STATUS_REG_1, &val);102		if (status)103			goto err;104 105		status = smb_wr16(SECOCEC_STATUS_REG_1, val);106		if (status)107			goto err;108 109		/* Enable the interrupts */110		status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);111		if (status)112			goto err;113 114		status = smb_wr16(SECOCEC_ENABLE_REG_1,115				  val | SECOCEC_ENABLE_REG_1_CEC);116		if (status)117			goto err;118 119		dev_dbg(dev, "Device enabled\n");120	} else {121		/* Clear the status register */122		status = smb_rd16(SECOCEC_STATUS_REG_1, &val);123		status = smb_wr16(SECOCEC_STATUS_REG_1, val);124 125		/* Disable the interrupts */126		status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);127		status = smb_wr16(SECOCEC_ENABLE_REG_1, val &128				  ~SECOCEC_ENABLE_REG_1_CEC &129				  ~SECOCEC_ENABLE_REG_1_IR);130 131		dev_dbg(dev, "Device disabled\n");132	}133 134	return 0;135err:136	return status;137}138 139static int secocec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)140{141	u16 enable_val = 0;142	int status;143 144	/* Disable device */145	status = smb_rd16(SECOCEC_ENABLE_REG_1, &enable_val);146	if (status)147		return status;148 149	status = smb_wr16(SECOCEC_ENABLE_REG_1,150			  enable_val & ~SECOCEC_ENABLE_REG_1_CEC);151	if (status)152		return status;153 154	/* Write logical address155	 * NOTE: CEC_LOG_ADDR_INVALID is mapped to the 'Unregistered' LA156	 */157	status = smb_wr16(SECOCEC_DEVICE_LA, logical_addr & 0xf);158	if (status)159		return status;160 161	/* Re-enable device */162	status = smb_wr16(SECOCEC_ENABLE_REG_1,163			  enable_val | SECOCEC_ENABLE_REG_1_CEC);164	if (status)165		return status;166 167	return 0;168}169 170static int secocec_adap_transmit(struct cec_adapter *adap, u8 attempts,171				 u32 signal_free_time, struct cec_msg *msg)172{173	u16 payload_len, payload_id_len, destination, val = 0;174	u8 *payload_msg;175	int status;176	u8 i;177 178	/* Device msg len already accounts for header */179	payload_id_len = msg->len - 1;180 181	/* Send data length */182	status = smb_wr16(SECOCEC_WRITE_DATA_LENGTH, payload_id_len);183	if (status)184		goto err;185 186	/* Send Operation ID if present */187	if (payload_id_len > 0) {188		status = smb_wr16(SECOCEC_WRITE_OPERATION_ID, msg->msg[1]);189		if (status)190			goto err;191	}192	/* Send data if present */193	if (payload_id_len > 1) {194		/* Only data; */195		payload_len = msg->len - 2;196		payload_msg = &msg->msg[2];197 198		/* Copy message into registers */199		for (i = 0; i < payload_len; i += 2) {200			/* hi byte */201			val = payload_msg[i + 1] << 8;202 203			/* lo byte */204			val |= payload_msg[i];205 206			status = smb_wr16(SECOCEC_WRITE_DATA_00 + i / 2, val);207			if (status)208				goto err;209		}210	}211	/* Send msg source/destination and fire msg */212	destination = msg->msg[0];213	status = smb_wr16(SECOCEC_WRITE_BYTE0, destination);214	if (status)215		goto err;216 217	return 0;218 219err:220	return status;221}222 223static void secocec_tx_done(struct cec_adapter *adap, u16 status_val)224{225	if (status_val & SECOCEC_STATUS_TX_ERROR_MASK) {226		if (status_val & SECOCEC_STATUS_TX_NACK_ERROR)227			cec_transmit_attempt_done(adap, CEC_TX_STATUS_NACK);228		else229			cec_transmit_attempt_done(adap, CEC_TX_STATUS_ERROR);230	} else {231		cec_transmit_attempt_done(adap, CEC_TX_STATUS_OK);232	}233 234	/* Reset status reg */235	status_val = SECOCEC_STATUS_TX_ERROR_MASK |236		SECOCEC_STATUS_MSG_SENT_MASK |237		SECOCEC_STATUS_TX_NACK_ERROR;238	smb_wr16(SECOCEC_STATUS, status_val);239}240 241static void secocec_rx_done(struct cec_adapter *adap, u16 status_val)242{243	struct secocec_data *cec = cec_get_drvdata(adap);244	struct device *dev = cec->dev;245	struct cec_msg msg = { };246	bool flag_overflow = false;247	u8 payload_len, i = 0;248	u8 *payload_msg;249	u16 val = 0;250	int status;251 252	if (status_val & SECOCEC_STATUS_RX_OVERFLOW_MASK) {253		/* NOTE: Untested, it also might not be necessary */254		dev_warn(dev, "Received more than 16 bytes. Discarding\n");255		flag_overflow = true;256	}257 258	if (status_val & SECOCEC_STATUS_RX_ERROR_MASK) {259		dev_warn(dev, "Message received with errors. Discarding\n");260		status = -EIO;261		goto rxerr;262	}263 264	/* Read message length */265	status = smb_rd16(SECOCEC_READ_DATA_LENGTH, &val);266	if (status)267		return;268 269	/* Device msg len already accounts for the header */270	msg.len = min(val + 1, CEC_MAX_MSG_SIZE);271 272	/* Read logical address */273	status = smb_rd16(SECOCEC_READ_BYTE0, &val);274	if (status)275		return;276 277	/* device stores source LA and destination */278	msg.msg[0] = val;279 280	/* Read operation ID */281	status = smb_rd16(SECOCEC_READ_OPERATION_ID, &val);282	if (status)283		return;284 285	msg.msg[1] = val;286 287	/* Read data if present */288	if (msg.len > 1) {289		payload_len = msg.len - 2;290		payload_msg = &msg.msg[2];291 292		/* device stores 2 bytes in every 16-bit val */293		for (i = 0; i < payload_len; i += 2) {294			status = smb_rd16(SECOCEC_READ_DATA_00 + i / 2, &val);295			if (status)296				return;297 298			/* low byte, skipping header */299			payload_msg[i] = val & 0x00ff;300 301			/* hi byte */302			payload_msg[i + 1] = (val & 0xff00) >> 8;303		}304	}305 306	cec_received_msg(cec->cec_adap, &msg);307 308	/* Reset status reg */309	status_val = SECOCEC_STATUS_MSG_RECEIVED_MASK;310	if (flag_overflow)311		status_val |= SECOCEC_STATUS_RX_OVERFLOW_MASK;312 313	status = smb_wr16(SECOCEC_STATUS, status_val);314 315	return;316 317rxerr:318	/* Reset error reg */319	status_val = SECOCEC_STATUS_MSG_RECEIVED_MASK |320		SECOCEC_STATUS_RX_ERROR_MASK;321	if (flag_overflow)322		status_val |= SECOCEC_STATUS_RX_OVERFLOW_MASK;323	smb_wr16(SECOCEC_STATUS, status_val);324}325 326static const struct cec_adap_ops secocec_cec_adap_ops = {327	/* Low-level callbacks */328	.adap_enable = secocec_adap_enable,329	.adap_log_addr = secocec_adap_log_addr,330	.adap_transmit = secocec_adap_transmit,331};332 333#ifdef CONFIG_CEC_SECO_RC334static int secocec_ir_probe(void *priv)335{336	struct secocec_data *cec = priv;337	struct device *dev = cec->dev;338	int status;339	u16 val;340 341	/* Prepare the RC input device */342	cec->ir = devm_rc_allocate_device(dev, RC_DRIVER_SCANCODE);343	if (!cec->ir)344		return -ENOMEM;345 346	snprintf(cec->ir_input_phys, sizeof(cec->ir_input_phys),347		 "%s/input0", dev_name(dev));348 349	cec->ir->device_name = dev_name(dev);350	cec->ir->input_phys = cec->ir_input_phys;351	cec->ir->input_id.bustype = BUS_HOST;352	cec->ir->input_id.vendor = 0;353	cec->ir->input_id.product = 0;354	cec->ir->input_id.version = 1;355	cec->ir->driver_name = SECOCEC_DEV_NAME;356	cec->ir->allowed_protocols = RC_PROTO_BIT_RC5;357	cec->ir->priv = cec;358	cec->ir->map_name = RC_MAP_HAUPPAUGE;359	cec->ir->timeout = MS_TO_US(100);360 361	/* Clear the status register */362	status = smb_rd16(SECOCEC_STATUS_REG_1, &val);363	if (status != 0)364		goto err;365 366	status = smb_wr16(SECOCEC_STATUS_REG_1, val);367	if (status != 0)368		goto err;369 370	/* Enable the interrupts */371	status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);372	if (status != 0)373		goto err;374 375	status = smb_wr16(SECOCEC_ENABLE_REG_1,376			  val | SECOCEC_ENABLE_REG_1_IR);377	if (status != 0)378		goto err;379 380	dev_dbg(dev, "IR enabled\n");381 382	status = devm_rc_register_device(dev, cec->ir);383 384	if (status) {385		dev_err(dev, "Failed to prepare input device\n");386		cec->ir = NULL;387		goto err;388	}389 390	return 0;391 392err:393	smb_rd16(SECOCEC_ENABLE_REG_1, &val);394 395	smb_wr16(SECOCEC_ENABLE_REG_1,396		 val & ~SECOCEC_ENABLE_REG_1_IR);397 398	dev_dbg(dev, "IR disabled\n");399	return status;400}401 402static int secocec_ir_rx(struct secocec_data *priv)403{404	struct secocec_data *cec = priv;405	struct device *dev = cec->dev;406	u16 val, status, key, addr, toggle;407 408	if (!cec->ir)409		return -ENODEV;410 411	status = smb_rd16(SECOCEC_IR_READ_DATA, &val);412	if (status != 0)413		goto err;414 415	key = val & SECOCEC_IR_COMMAND_MASK;416	addr = (val & SECOCEC_IR_ADDRESS_MASK) >> SECOCEC_IR_ADDRESS_SHL;417	toggle = (val & SECOCEC_IR_TOGGLE_MASK) >> SECOCEC_IR_TOGGLE_SHL;418 419	rc_keydown(cec->ir, RC_PROTO_RC5, RC_SCANCODE_RC5(addr, key), toggle);420 421	dev_dbg(dev, "IR key pressed: 0x%02x addr 0x%02x toggle 0x%02x\n", key,422		addr, toggle);423 424	return 0;425 426err:427	dev_err(dev, "IR Receive message failed (%d)\n", status);428	return -EIO;429}430#else431static void secocec_ir_rx(struct secocec_data *priv)432{433}434 435static int secocec_ir_probe(void *priv)436{437	return 0;438}439#endif440 441static irqreturn_t secocec_irq_handler(int irq, void *priv)442{443	struct secocec_data *cec = priv;444	struct device *dev = cec->dev;445	u16 status_val, cec_val, val = 0;446	int status;447 448	/*  Read status register */449	status = smb_rd16(SECOCEC_STATUS_REG_1, &status_val);450	if (status)451		goto err;452 453	if (status_val & SECOCEC_STATUS_REG_1_CEC) {454		/* Read CEC status register */455		status = smb_rd16(SECOCEC_STATUS, &cec_val);456		if (status)457			goto err;458 459		if (cec_val & SECOCEC_STATUS_MSG_RECEIVED_MASK)460			secocec_rx_done(cec->cec_adap, cec_val);461 462		if (cec_val & SECOCEC_STATUS_MSG_SENT_MASK)463			secocec_tx_done(cec->cec_adap, cec_val);464 465		if ((~cec_val & SECOCEC_STATUS_MSG_SENT_MASK) &&466		    (~cec_val & SECOCEC_STATUS_MSG_RECEIVED_MASK))467			dev_warn_once(dev,468				      "Message not received or sent, but interrupt fired");469 470		val = SECOCEC_STATUS_REG_1_CEC;471	}472 473	if (status_val & SECOCEC_STATUS_REG_1_IR) {474		val |= SECOCEC_STATUS_REG_1_IR;475 476		secocec_ir_rx(cec);477	}478 479	/*  Reset status register */480	status = smb_wr16(SECOCEC_STATUS_REG_1, val);481	if (status)482		goto err;483 484	return IRQ_HANDLED;485 486err:487	dev_err_once(dev, "IRQ: R/W SMBus operation failed %d\n", status);488 489	/*  Reset status register */490	val = SECOCEC_STATUS_REG_1_CEC | SECOCEC_STATUS_REG_1_IR;491	smb_wr16(SECOCEC_STATUS_REG_1, val);492 493	return IRQ_HANDLED;494}495 496struct cec_dmi_match {497	const char *sys_vendor;498	const char *product_name;499	const char *devname;500	const char *conn;501};502 503static const struct cec_dmi_match secocec_dmi_match_table[] = {504	/* UDOO X86 */505	{ "SECO", "UDOO x86", "0000:00:02.0", "Port B" },506};507 508static struct device *secocec_cec_find_hdmi_dev(struct device *dev,509						const char **conn)510{511	int i;512 513	for (i = 0 ; i < ARRAY_SIZE(secocec_dmi_match_table) ; ++i) {514		const struct cec_dmi_match *m = &secocec_dmi_match_table[i];515 516		if (dmi_match(DMI_SYS_VENDOR, m->sys_vendor) &&517		    dmi_match(DMI_PRODUCT_NAME, m->product_name)) {518			struct device *d;519 520			/* Find the device, bail out if not yet registered */521			d = bus_find_device_by_name(&pci_bus_type, NULL,522						    m->devname);523			if (!d)524				return ERR_PTR(-EPROBE_DEFER);525 526			put_device(d);527			*conn = m->conn;528			return d;529		}530	}531 532	return ERR_PTR(-EINVAL);533}534 535static int secocec_acpi_probe(struct secocec_data *sdev)536{537	struct device *dev = sdev->dev;538	struct gpio_desc *gpio;539	int irq = 0;540 541	gpio = devm_gpiod_get(dev, NULL, GPIOD_IN);542	if (IS_ERR(gpio)) {543		dev_err(dev, "Cannot request interrupt gpio\n");544		return PTR_ERR(gpio);545	}546 547	irq = gpiod_to_irq(gpio);548	if (irq < 0) {549		dev_err(dev, "Cannot find valid irq\n");550		return -ENODEV;551	}552	dev_dbg(dev, "irq-gpio is bound to IRQ %d\n", irq);553 554	sdev->irq = irq;555 556	return 0;557}558 559static int secocec_probe(struct platform_device *pdev)560{561	struct secocec_data *secocec;562	struct device *dev = &pdev->dev;563	struct device *hdmi_dev;564	const char *conn = NULL;565	int ret;566	u16 val;567 568	hdmi_dev = secocec_cec_find_hdmi_dev(&pdev->dev, &conn);569	if (IS_ERR(hdmi_dev))570		return PTR_ERR(hdmi_dev);571 572	secocec = devm_kzalloc(dev, sizeof(*secocec), GFP_KERNEL);573	if (!secocec)574		return -ENOMEM;575 576	dev_set_drvdata(dev, secocec);577 578	/* Request SMBus regions */579	if (!request_muxed_region(BRA_SMB_BASE_ADDR, 7, "CEC00001")) {580		dev_err(dev, "Request memory region failed\n");581		return -ENXIO;582	}583 584	secocec->pdev = pdev;585	secocec->dev = dev;586 587	if (!has_acpi_companion(dev)) {588		dev_dbg(dev, "Cannot find any ACPI companion\n");589		ret = -ENODEV;590		goto err;591	}592 593	ret = secocec_acpi_probe(secocec);594	if (ret) {595		dev_err(dev, "Cannot assign gpio to IRQ\n");596		ret = -ENODEV;597		goto err;598	}599 600	/* Firmware version check */601	ret = smb_rd16(SECOCEC_VERSION, &val);602	if (ret) {603		dev_err(dev, "Cannot check fw version\n");604		goto err;605	}606	if (val < SECOCEC_LATEST_FW) {607		dev_err(dev, "CEC Firmware not supported (v.%04x). Use ver > v.%04x\n",608			val, SECOCEC_LATEST_FW);609		ret = -EINVAL;610		goto err;611	}612 613	ret = devm_request_threaded_irq(dev,614					secocec->irq,615					NULL,616					secocec_irq_handler,617					IRQF_TRIGGER_RISING | IRQF_ONESHOT,618					dev_name(&pdev->dev), secocec);619 620	if (ret) {621		dev_err(dev, "Cannot request IRQ %d\n", secocec->irq);622		ret = -EIO;623		goto err;624	}625 626	/* Allocate CEC adapter */627	secocec->cec_adap = cec_allocate_adapter(&secocec_cec_adap_ops,628						 secocec,629						 dev_name(dev),630						 CEC_CAP_DEFAULTS |631						 CEC_CAP_CONNECTOR_INFO,632						 SECOCEC_MAX_ADDRS);633 634	if (IS_ERR(secocec->cec_adap)) {635		ret = PTR_ERR(secocec->cec_adap);636		goto err;637	}638 639	secocec->notifier = cec_notifier_cec_adap_register(hdmi_dev, conn,640							   secocec->cec_adap);641	if (!secocec->notifier) {642		ret = -ENOMEM;643		goto err_delete_adapter;644	}645 646	ret = cec_register_adapter(secocec->cec_adap, dev);647	if (ret)648		goto err_notifier;649 650	ret = secocec_ir_probe(secocec);651	if (ret)652		goto err_notifier;653 654	platform_set_drvdata(pdev, secocec);655 656	dev_dbg(dev, "Device registered\n");657 658	return ret;659 660err_notifier:661	cec_notifier_cec_adap_unregister(secocec->notifier, secocec->cec_adap);662err_delete_adapter:663	cec_delete_adapter(secocec->cec_adap);664err:665	release_region(BRA_SMB_BASE_ADDR, 7);666	dev_err(dev, "%s device probe failed\n", dev_name(dev));667 668	return ret;669}670 671static void secocec_remove(struct platform_device *pdev)672{673	struct secocec_data *secocec = platform_get_drvdata(pdev);674	u16 val;675 676	if (secocec->ir) {677		smb_rd16(SECOCEC_ENABLE_REG_1, &val);678 679		smb_wr16(SECOCEC_ENABLE_REG_1, val & ~SECOCEC_ENABLE_REG_1_IR);680 681		dev_dbg(&pdev->dev, "IR disabled\n");682	}683	cec_notifier_cec_adap_unregister(secocec->notifier, secocec->cec_adap);684	cec_unregister_adapter(secocec->cec_adap);685 686	release_region(BRA_SMB_BASE_ADDR, 7);687 688	dev_dbg(&pdev->dev, "CEC device removed\n");689}690 691#ifdef CONFIG_PM_SLEEP692static int secocec_suspend(struct device *dev)693{694	int status;695	u16 val;696 697	dev_dbg(dev, "Device going to suspend, disabling\n");698 699	/* Clear the status register */700	status = smb_rd16(SECOCEC_STATUS_REG_1, &val);701	if (status)702		goto err;703 704	status = smb_wr16(SECOCEC_STATUS_REG_1, val);705	if (status)706		goto err;707 708	/* Disable the interrupts */709	status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);710	if (status)711		goto err;712 713	status = smb_wr16(SECOCEC_ENABLE_REG_1, val &714			  ~SECOCEC_ENABLE_REG_1_CEC & ~SECOCEC_ENABLE_REG_1_IR);715	if (status)716		goto err;717 718	return 0;719 720err:721	dev_err(dev, "Suspend failed: %d\n", status);722	return status;723}724 725static int secocec_resume(struct device *dev)726{727	int status;728	u16 val;729 730	dev_dbg(dev, "Resuming device from suspend\n");731 732	/* Clear the status register */733	status = smb_rd16(SECOCEC_STATUS_REG_1, &val);734	if (status)735		goto err;736 737	status = smb_wr16(SECOCEC_STATUS_REG_1, val);738	if (status)739		goto err;740 741	/* Enable the interrupts */742	status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);743	if (status)744		goto err;745 746	status = smb_wr16(SECOCEC_ENABLE_REG_1, val | SECOCEC_ENABLE_REG_1_CEC);747	if (status)748		goto err;749 750	dev_dbg(dev, "Device resumed from suspend\n");751 752	return 0;753 754err:755	dev_err(dev, "Resume failed: %d\n", status);756	return status;757}758 759static SIMPLE_DEV_PM_OPS(secocec_pm_ops, secocec_suspend, secocec_resume);760#define SECOCEC_PM_OPS (&secocec_pm_ops)761#else762#define SECOCEC_PM_OPS NULL763#endif764 765#ifdef CONFIG_ACPI766static const struct acpi_device_id secocec_acpi_match[] = {767	{"CEC00001", 0},768	{},769};770 771MODULE_DEVICE_TABLE(acpi, secocec_acpi_match);772#endif773 774static struct platform_driver secocec_driver = {775	.driver = {776		   .name = SECOCEC_DEV_NAME,777		   .acpi_match_table = ACPI_PTR(secocec_acpi_match),778		   .pm = SECOCEC_PM_OPS,779	},780	.probe = secocec_probe,781	.remove_new = secocec_remove,782};783 784module_platform_driver(secocec_driver);785 786MODULE_DESCRIPTION("SECO CEC X86 Driver");787MODULE_AUTHOR("Ettore Chimenti <ek5.chimenti@gmail.com>");788MODULE_LICENSE("Dual BSD/GPL");789