/* Copyright (C) MOXA Inc. All rights reserved.

   This is free software distributed under the terms of the
   GNU Public License.  See the file COPYING-GPL for details.
*/

/*
    mxcandrv2.c

    Routines to support Moxa CAN FPGA card.

    2025-12-09	Eric XH Lin
		new release
*/

/*
 * Derived from the ems_pci.c driver:
 *	Copyright (C) 2007 Wolfgang Grandegger <wg@grandegger.com>
 *	Copyright (C) 2008 Markus Plessing <plessing@ems-wuensche.com>
 *	Copyright (C) 2008 Sebastian Haas <haas@ems-wuensche.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the version 2 of the GNU General Public License
 * as published by the Free Software Foundation
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <linux/kernel.h>
#include <linux/bitops.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/pci.h>
#include <linux/pm.h>
#include <linux/version.h>

#include "moxa_can.h"

MODULE_DESCRIPTION("Socket-CAN driver for Moxa CP602_FPGA series CAN card");
MODULE_LICENSE("GPL v2");

#define DRV_NAME				"mxcandrv2"
#define	VERSION_CODE(ver,rel,seq)	((ver << 16) | (rel << 8) | seq)

#define MOXA_PCI_BOARD_BASE_BAR	0
#define MOXA_PCI_CAN0_BASE_BAR	1
#define MOXA_PCI_CAN1_BASE_BAR	2
#define MOXA_PCI_BOARD_CONF_SIZE 256 
#define MOXA_PCI_CAN_CONF_SIZE  8192
#define MOXA_PCI_MAX_CHAN		2

//#define USE_LEGACY_IRQ 1
#define FORCE_DEASSERT_IRQ 1

#if 0
#define mxprint(...) printk("mx: "__VA_ARGS__)
#else
#define mxprint(...)
#endif

struct moxa_pci_card {
	int channels;
	struct pci_dev *pci_dev;
	struct net_device *net_dev[MOXA_PCI_MAX_CHAN];
    void __iomem *board_addr;
    int use_msi;
	unsigned long open_mask;
};

struct moxa_pci_can_info {
	const char * model_name;
	int channel_count;
};

static struct moxa_pci_can_info moxa_can_cp602u_fpga = {
	.model_name = "CP602U_I",
	.channel_count = 2,
};

static struct moxa_pci_can_info moxa_can_cp602e_fpga = {
	.model_name = "CP602E_I",
	.channel_count = 2,
};

static struct moxa_pci_can_info moxa_can_cb602_fpga = {
	.model_name = "CB602_I",
	.channel_count = 2,
};

static const struct pci_device_id moxa_pci_tbl[] = {
	{PCI_VENDOR_ID_MOXA, 0x6023, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (kernel_ulong_t)&moxa_can_cp602u_fpga},
	{PCI_VENDOR_ID_MOXA, 0x6024, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (kernel_ulong_t)&moxa_can_cp602e_fpga},
	{PCI_VENDOR_ID_MOXA, 0x6025, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (kernel_ulong_t)&moxa_can_cb602_fpga},
	{0,}
};
MODULE_DEVICE_TABLE(pci, moxa_pci_tbl);

static void moxa_pci_deassert_irq(struct moxa_pci_card *card)
{
#ifdef FORCE_DEASSERT_IRQ
	uint32_t fpga_val;

	*(uint8_t *)(card->board_addr + 0x002) = 0x16;
	fpga_val = *(uint8_t *)(card->board_addr + 0x02);
	dev_info(&card->pci_dev->dev, "Forced de-assertion: 0x%X\n", fpga_val);
#endif
}

static void moxa_pci_del_card(struct pci_dev *pdev)
{
	struct moxa_pci_card *card = pci_get_drvdata(pdev);
	struct net_device *ndev;
	struct moxa_can_priv *priv;
	int i;

	for (i = 0; i < card->channels; i++) {
		ndev = card->net_dev[i];
		if (!ndev)
			continue;

		priv = netdev_priv(ndev);
		dev_info(&pdev->dev, "Removing %s.\n", ndev->name);
		moxa_can_unregister(ndev);
		moxa_can_free(ndev);
	}

	if (card->board_addr)
		pci_iounmap(pdev, card->board_addr);

    if( card->use_msi )
        pci_disable_msi(pdev);

    pci_disable_device(pdev);
}

static int moxa_pci_add_card(struct pci_dev *pdev, const struct pci_device_id *ent)
{
	struct moxa_pci_card *card;
	struct moxa_pci_can_info *mxinfo = (struct moxa_pci_can_info *)ent->driver_data;
	struct net_device *ndev;
	struct moxa_can_priv *priv;
	int err, i;
	void __iomem *addr;
	uint32_t fpga_val=0;
	
	if (pci_enable_device(pdev) < 0) {
		dev_err(&pdev->dev, "Enabling PCI device failed\n");
		return -ENODEV;
	}

	card = devm_kzalloc(&pdev->dev, sizeof(struct moxa_pci_card), GFP_KERNEL);
	if (card == NULL) {
        pci_disable_device(pdev);
		return -ENOMEM;
	}

	pci_set_drvdata(pdev, card);
	card->pci_dev = pdev;
	card->channels = mxinfo->channel_count;

    pci_set_master(pdev);

#ifdef USE_LEGACY_IRQ
    err = -1;
#else
    err = pci_enable_msi(pdev);
#endif
    if( err==0 ){
        card->use_msi = 1;
    }

	/* Map board config BAR */
	card->board_addr = pci_iomap(pdev, MOXA_PCI_BOARD_BASE_BAR, MOXA_PCI_BOARD_CONF_SIZE);
	if (card->board_addr == NULL) {
		dev_err(&pdev->dev, "Failed to map board config\n");
		err = -ENOMEM;
		goto failure_cleanup;
	}

	fpga_val = *(uint32_t*)(card->board_addr+0xf0);
	dev_info(&pdev->dev, "%s-FPGA Code: %08X\n", mxinfo->model_name, fpga_val);

#ifdef FORCE_DEASSERT_IRQ
    *(uint8_t*)(card->board_addr+0x002) = 0x16;
    fpga_val = *(uint8_t*)(card->board_addr+0x02);
    dev_info(&pdev->dev, "Forced de-assertion: 0x%X\n", fpga_val);
#endif

	 /* Detect available channels */
	for (i = 0; i < card->channels; i++) {
		int bar = (i == 0) ? MOXA_PCI_CAN0_BASE_BAR : MOXA_PCI_CAN1_BASE_BAR;

		//printk("%s(%d) bar=%d\n", __func__, __LINE__, bar);
		addr = pci_iomap(pdev, bar, MOXA_PCI_CAN_CONF_SIZE);
		if (!addr) {
			dev_err(&pdev->dev, "Failed to map CAN channel %d\n", i);
			err = -ENOMEM;
			goto failure_cleanup;
		}

		ndev = moxa_can_alloc(&pdev->dev);
		if(ndev == NULL) {
			err = -ENOMEM;
			goto failure_cleanup;
		}

		priv = netdev_priv(ndev);
		priv->ctrl = (volatile CAN_CTRL_t *)addr;
		//priv->reg_base = addr;
		priv->channel = i;
		ndev->irq = pdev->irq; 
		ndev->dev_id = i;
		
		card->net_dev[i] = ndev;
		
		err = moxa_can_register(ndev);
		if (err) {
			dev_err(&pdev->dev, "Registering device failed (err=%d)\n", err);
			moxa_can_free(ndev);
			goto failure_cleanup;
		}
		
		dev_info(&pdev->dev, "%s %s channel %d registered at 0x%p, irq %d\n",
			 mxinfo->model_name, ndev->name, i, addr, ndev->irq);
	}
	return 0;

failure_cleanup:
    if(card->use_msi){
        pci_disable_msi(pdev);
    }
    pci_disable_device(pdev);
	dev_err(&pdev->dev, "Error: %d. Cleaning Up.\n", err);
	moxa_pci_del_card(pdev);
	return err;
}

static int moxa_pci_suspend(struct pci_dev *pdev, pm_message_t state)
{
	struct moxa_pci_card *card = pci_get_drvdata(pdev);
	struct net_device *ndev;
	int i;

	if (!card)
		return 0;

	card->open_mask = 0;
	for (i = 0; i < card->channels; i++) {
		ndev = card->net_dev[i];
		if (!ndev || !netif_running(ndev))
			continue;

		card->open_mask |= BIT(i);
		netif_device_detach(ndev);
		moxa_can_suspend(ndev);
	}

	pci_save_state(pdev);

	if (card->use_msi) {
		pci_disable_msi(pdev);
		card->use_msi = 0;
	}

	pci_disable_device(pdev);
	pci_set_power_state(pdev, pci_choose_state(pdev, state));

	return 0;
}

static int moxa_pci_resume(struct pci_dev *pdev)
{
	struct moxa_pci_card *card = pci_get_drvdata(pdev);
	struct net_device *ndev;
	int err, i;

	if (!card)
		return 0;

	pci_set_power_state(pdev, PCI_D0);

	err = pci_enable_device(pdev);
	if (err) {
		dev_err(&pdev->dev, "Enabling PCI device failed after resume\n");
		return err;
	}

	pci_restore_state(pdev);
	pci_set_master(pdev);

#ifdef USE_LEGACY_IRQ
	err = -1;
#else
	err = pci_enable_msi(pdev);
#endif
	card->use_msi = (err == 0);

	if (card->board_addr)
		moxa_pci_deassert_irq(card);

	for (i = 0; i < card->channels; i++) {
		ndev = card->net_dev[i];
		if (!ndev)
			continue;

		ndev->irq = pdev->irq;

		if (!(card->open_mask & BIT(i)))
			continue;

		err = moxa_can_resume(ndev);
		if (err) {
			dev_err(&pdev->dev, "Failed to resume %s (err=%d)\n",
				ndev->name, err);
			return err;
		}

		netif_device_attach(ndev);
	}

	return 0;
}

static struct pci_driver moxa_pci_driver = {
	.name = DRV_NAME,
	.id_table = moxa_pci_tbl,
	.probe = moxa_pci_add_card,
	.remove = moxa_pci_del_card,
	.suspend = moxa_pci_suspend,
	.resume = moxa_pci_resume,
};

static int __init moxa_pci_init(void)
{
	int res;
	mxprint("%s IN\n", __FUNCTION__);

	res = pci_register_driver(&moxa_pci_driver);

	mxprint("%s OUT\n", __FUNCTION__);
	return res;
}
module_init(moxa_pci_init);

static void __exit moxa_pci_exit(void)
{
	mxprint("%s IN\n", __FUNCTION__);

	pci_unregister_driver(&moxa_pci_driver);

	mxprint("%s OUT\n", __FUNCTION__);
}
module_exit(moxa_pci_exit);
